implements PR030
This commit is contained in:
parent
01c5b6649a
commit
495104db6d
@ -179,6 +179,8 @@ At minimum, the PBS diagnostics baseline must cover:
|
|||||||
5. malformed, unauthorized, or capability-rejected host usage required by `6.2. Host ABI Binding and Loader Resolution Specification.md` and `7. Cartridge Manifest and Runtime Capabilities Specification.md`,
|
5. malformed, unauthorized, or capability-rejected host usage required by `6.2. Host ABI Binding and Loader Resolution Specification.md` and `7. Cartridge Manifest and Runtime Capabilities Specification.md`,
|
||||||
6. source-attributable backend-originated failures that remain user-actionable under normative lowering or load-facing rules.
|
6. source-attributable backend-originated failures that remain user-actionable under normative lowering or load-facing rules.
|
||||||
|
|
||||||
|
At minimum, host-admission diagnostics must cover missing or malformed host capability metadata and unknown or undeclared capability names.
|
||||||
|
|
||||||
Only backend-originated failures that remain source-attributable and user-actionable belong to the PBS-facing diagnostics contract.
|
Only backend-originated failures that remain source-attributable and user-actionable belong to the PBS-facing diagnostics contract.
|
||||||
|
|
||||||
This means:
|
This means:
|
||||||
|
|||||||
@ -75,6 +75,7 @@ For each admitted source unit and callable in the current lowering slice, `IRBac
|
|||||||
3. declared return surface information,
|
3. declared return surface information,
|
||||||
4. source attribution anchor (`file + span`) for diagnostics and traceability,
|
4. source attribution anchor (`file + span`) for diagnostics and traceability,
|
||||||
5. source-observable parse intent for statement/expression structure (including precedence/associativity outcome already fixed by AST shape).
|
5. source-observable parse intent for statement/expression structure (including precedence/associativity outcome already fixed by AST shape).
|
||||||
|
6. deterministic `requiredCapabilities` derived from admitted host-binding metadata for packer/runtime-manifest assistance.
|
||||||
|
|
||||||
Lowering must not collapse source categories in a way that erases required declaration/callable identity needed by downstream diagnostics or conformance assertions.
|
Lowering must not collapse source categories in a way that erases required declaration/callable identity needed by downstream diagnostics or conformance assertions.
|
||||||
|
|
||||||
|
|||||||
@ -62,8 +62,9 @@ Rules:
|
|||||||
- Attributes are not first-class values and are not reflectable in v1 core.
|
- Attributes are not first-class values and are not reflectable in v1 core.
|
||||||
- Attributes do not automatically survive into runtime or bytecode artifacts.
|
- Attributes do not automatically survive into runtime or bytecode artifacts.
|
||||||
- An attribute affects runtime artifacts only when another specification defines an explicit lowering for its semantic effect.
|
- An attribute affects runtime artifacts only when another specification defines an explicit lowering for its semantic effect.
|
||||||
- In v1 core, the normative reserved attributes are `Host`, `BuiltinType`, `BuiltinConst`, and `IntrinsicCall`.
|
- In v1 core, the normative reserved attributes are `Host`, `Capability`, `BuiltinType`, `BuiltinConst`, and `IntrinsicCall`.
|
||||||
- `Host` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
|
- `Host` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
|
||||||
|
- `Capability` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
|
||||||
- `Host` is invalid on ordinary user-authored modules, top-level `fn`, struct methods, service methods, callbacks, contracts, and constants.
|
- `Host` is invalid on ordinary user-authored modules, top-level `fn`, struct methods, service methods, callbacks, contracts, and constants.
|
||||||
- `Host` metadata is consumed by the compiler during host-binding lowering.
|
- `Host` metadata is consumed by the compiler during host-binding lowering.
|
||||||
- The `Host` attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification.
|
- The `Host` attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification.
|
||||||
@ -168,9 +169,12 @@ Rules:
|
|||||||
- Any payload-less `optional` type surface is statically invalid.
|
- Any payload-less `optional` type surface is statically invalid.
|
||||||
- Any `optional void` type surface is statically invalid.
|
- Any `optional void` type surface is statically invalid.
|
||||||
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` attribute.
|
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` attribute.
|
||||||
|
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Capability` attribute.
|
||||||
- A `Host` attribute MUST declare exactly the named arguments `module`, `name`, and `version`.
|
- A `Host` attribute MUST declare exactly the named arguments `module`, `name`, and `version`.
|
||||||
- `Host.module` and `Host.name` MUST be non-empty string literals.
|
- `Host.module` and `Host.name` MUST be non-empty string literals.
|
||||||
- `Host.version` MUST be a positive integer literal.
|
- `Host.version` MUST be a positive integer literal.
|
||||||
|
- A `Capability` attribute MUST declare exactly the named argument `name`.
|
||||||
|
- `Capability.name` MUST be a non-empty lowercase capability identifier.
|
||||||
- Two host methods in the same resolved stdlib environment MUST NOT lower to the same canonical `(module, name, version)` unless they denote the same host method declaration after project/module resolution.
|
- Two host methods in the same resolved stdlib environment MUST NOT lower to the same canonical `(module, name, version)` unless they denote the same host method declaration after project/module resolution.
|
||||||
- A `declare const` declaration must include an explicit type annotation.
|
- A `declare const` declaration must include an explicit type annotation.
|
||||||
- A `declare const` declaration without `BuiltinConst` metadata must include an initializer.
|
- A `declare const` declaration without `BuiltinConst` metadata must include an initializer.
|
||||||
@ -655,6 +659,7 @@ At minimum, deterministic static diagnostics are required for:
|
|||||||
- invalid host method declaration shape,
|
- invalid host method declaration shape,
|
||||||
- invalid attribute surface,
|
- invalid attribute surface,
|
||||||
- invalid `Host` attribute target,
|
- invalid `Host` attribute target,
|
||||||
|
- invalid `Capability` attribute target,
|
||||||
- invalid `BuiltinType` attribute target,
|
- invalid `BuiltinType` attribute target,
|
||||||
- invalid `BuiltinConst` attribute target,
|
- invalid `BuiltinConst` attribute target,
|
||||||
- invalid `IntrinsicCall` attribute target,
|
- invalid `IntrinsicCall` attribute target,
|
||||||
@ -663,6 +668,8 @@ At minimum, deterministic static diagnostics are required for:
|
|||||||
- malformed `Host(module=..., name=..., version=...)` argument set,
|
- malformed `Host(module=..., name=..., version=...)` argument set,
|
||||||
- invalid empty `Host.module` or `Host.name`,
|
- invalid empty `Host.module` or `Host.name`,
|
||||||
- invalid non-positive `Host.version`,
|
- invalid non-positive `Host.version`,
|
||||||
|
- malformed `Capability(name=...)` argument set,
|
||||||
|
- invalid empty or non-canonical `Capability.name`,
|
||||||
- invalid builtin declaration shape,
|
- invalid builtin declaration shape,
|
||||||
- invalid builtin method declaration shape,
|
- invalid builtin method declaration shape,
|
||||||
- missing required `BuiltinType` attribute on a reserved builtin declaration,
|
- missing required `BuiltinType` attribute on a reserved builtin declaration,
|
||||||
|
|||||||
@ -118,6 +118,7 @@ The source-level call may originate from an SDK declaration such as:
|
|||||||
```pbs
|
```pbs
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, c: color);
|
fn draw_pixel(x: int, y: int, c: color);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -287,6 +288,7 @@ Capability gating is mandatory during load.
|
|||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- every resolved host binding declares required capabilities,
|
- every resolved host binding declares required capabilities,
|
||||||
|
- frontend/build host-admission computes deterministic `requiredCapabilities` from host binding metadata for packer assistance,
|
||||||
- the cartridge manifest declares requested capabilities using a nominal capability list,
|
- the cartridge manifest declares requested capabilities using a nominal capability list,
|
||||||
- the loader derives or receives the granted capability set from the cartridge/platform policy environment,
|
- the loader derives or receives the granted capability set from the cartridge/platform policy environment,
|
||||||
- if a required capability is not granted, load fails,
|
- if a required capability is not granted, load fails,
|
||||||
|
|||||||
@ -181,6 +181,7 @@ Compiler:
|
|||||||
- compiles source to PBX,
|
- compiles source to PBX,
|
||||||
- emits `SYSC`,
|
- emits `SYSC`,
|
||||||
- emits `HOSTCALL <sysc_index>`,
|
- emits `HOSTCALL <sysc_index>`,
|
||||||
|
- computes deterministic `requiredCapabilities` from admitted host bindings for packer tooling,
|
||||||
- does not grant authority.
|
- does not grant authority.
|
||||||
|
|
||||||
Packer:
|
Packer:
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package p.studio.compiler.pbs;
|
|||||||
import p.studio.compiler.models.IRFunction;
|
import p.studio.compiler.models.IRFunction;
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.models.IRBackendFile;
|
import p.studio.compiler.models.IRBackendFile;
|
||||||
|
import p.studio.compiler.messages.HostAdmissionContext;
|
||||||
|
import p.studio.compiler.models.IRReservedMetadata;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.pbs.lexer.PbsLexer;
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor;
|
import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor;
|
||||||
@ -19,6 +21,7 @@ public final class PbsFrontendCompiler {
|
|||||||
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
|
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
|
||||||
private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator();
|
private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator();
|
||||||
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
|
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
|
||||||
|
private final PbsHostAdmissionValidator hostAdmissionValidator = new PbsHostAdmissionValidator();
|
||||||
|
|
||||||
public IRBackendFile compileFile(
|
public IRBackendFile compileFile(
|
||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
@ -32,13 +35,22 @@ public final class PbsFrontendCompiler {
|
|||||||
final String source,
|
final String source,
|
||||||
final DiagnosticSink diagnostics,
|
final DiagnosticSink diagnostics,
|
||||||
final SourceKind sourceKind) {
|
final SourceKind sourceKind) {
|
||||||
|
return compileFile(fileId, source, diagnostics, sourceKind, HostAdmissionContext.permissiveDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRBackendFile compileFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final String source,
|
||||||
|
final DiagnosticSink diagnostics,
|
||||||
|
final SourceKind sourceKind,
|
||||||
|
final HostAdmissionContext hostAdmissionContext) {
|
||||||
final var admissionBaseline = diagnostics.errorCount();
|
final var admissionBaseline = diagnostics.errorCount();
|
||||||
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||||
final var parseMode = sourceKind == SourceKind.SDK_INTERFACE
|
final var parseMode = sourceKind == SourceKind.SDK_INTERFACE
|
||||||
? PbsParser.ParseMode.INTERFACE_MODULE
|
? PbsParser.ParseMode.INTERFACE_MODULE
|
||||||
: PbsParser.ParseMode.ORDINARY;
|
: PbsParser.ParseMode.ORDINARY;
|
||||||
final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode);
|
final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode);
|
||||||
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind);
|
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind, hostAdmissionContext);
|
||||||
if (diagnostics.errorCount() > admissionBaseline) {
|
if (diagnostics.errorCount() > admissionBaseline) {
|
||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
}
|
}
|
||||||
@ -57,6 +69,15 @@ public final class PbsFrontendCompiler {
|
|||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
final DiagnosticSink diagnostics,
|
final DiagnosticSink diagnostics,
|
||||||
final SourceKind sourceKind) {
|
final SourceKind sourceKind) {
|
||||||
|
return compileParsedFile(fileId, ast, diagnostics, sourceKind, HostAdmissionContext.permissiveDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRBackendFile compileParsedFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final DiagnosticSink diagnostics,
|
||||||
|
final SourceKind sourceKind,
|
||||||
|
final HostAdmissionContext hostAdmissionContext) {
|
||||||
final var semanticsErrorBaseline = diagnostics.errorCount();
|
final var semanticsErrorBaseline = diagnostics.errorCount();
|
||||||
declarationSemanticsValidator.validate(ast, sourceKind, diagnostics);
|
declarationSemanticsValidator.validate(ast, sourceKind, diagnostics);
|
||||||
flowSemanticsValidator.validate(ast, diagnostics);
|
flowSemanticsValidator.validate(ast, diagnostics);
|
||||||
@ -64,7 +85,20 @@ public final class PbsFrontendCompiler {
|
|||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind);
|
final var extractedReservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind);
|
||||||
|
final var hostAdmissionErrorBaseline = diagnostics.errorCount();
|
||||||
|
final var requiredCapabilities = hostAdmissionValidator.validate(
|
||||||
|
extractedReservedMetadata,
|
||||||
|
hostAdmissionContext,
|
||||||
|
diagnostics);
|
||||||
|
if (diagnostics.errorCount() > hostAdmissionErrorBaseline) {
|
||||||
|
return IRBackendFile.empty(fileId);
|
||||||
|
}
|
||||||
|
final var reservedMetadata = new IRReservedMetadata(
|
||||||
|
extractedReservedMetadata.hostMethodBindings(),
|
||||||
|
extractedReservedMetadata.builtinTypeSurfaces(),
|
||||||
|
extractedReservedMetadata.builtinConstSurfaces(),
|
||||||
|
requiredCapabilities);
|
||||||
|
|
||||||
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
||||||
? ReadOnlyList.empty()
|
? ReadOnlyList.empty()
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
|
public enum PbsHostAdmissionErrors {
|
||||||
|
E_HOST_MISSING_REQUIRED_CAPABILITY,
|
||||||
|
E_HOST_MALFORMED_CAPABILITY_METADATA,
|
||||||
|
E_HOST_UNKNOWN_CAPABILITY,
|
||||||
|
E_HOST_CAPABILITY_NOT_DECLARED,
|
||||||
|
E_HOST_INCONSISTENT_BINDING_CAPABILITY,
|
||||||
|
}
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
|
import p.studio.compiler.messages.HostAdmissionContext;
|
||||||
|
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.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class PbsHostAdmissionValidator {
|
||||||
|
private static final Pattern CAPABILITY_NAME = Pattern.compile("^[a-z][a-z0-9_]*$");
|
||||||
|
|
||||||
|
public ReadOnlyList<String> validate(
|
||||||
|
final IRReservedMetadata metadata,
|
||||||
|
final HostAdmissionContext context,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var knownCapabilities = normalizedSet(context.knownCapabilities());
|
||||||
|
final var declaredCapabilities = normalizedSet(context.declaredCapabilities());
|
||||||
|
final var requiredCapabilities = new LinkedHashSet<String>();
|
||||||
|
final var firstCapabilityByHostBinding = new HashMap<String, FirstBindingCapability>();
|
||||||
|
|
||||||
|
for (final var hostBinding : metadata.hostMethodBindings()) {
|
||||||
|
final var normalizedCapability = normalize(hostBinding.requiredCapability());
|
||||||
|
if (!hostBinding.capabilityDeclared()) {
|
||||||
|
Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name(),
|
||||||
|
"Host binding '%s.%s' requires Capability(name=...) metadata"
|
||||||
|
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||||
|
hostBinding.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (normalizedCapability.isBlank()) {
|
||||||
|
Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
|
||||||
|
"Host binding '%s.%s' has malformed Capability(name=...) metadata"
|
||||||
|
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||||
|
hostBinding.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CAPABILITY_NAME.matcher(normalizedCapability).matches()) {
|
||||||
|
Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
|
||||||
|
"Host binding '%s.%s' has malformed capability name '%s'"
|
||||||
|
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
|
||||||
|
hostBinding.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!knownCapabilities.contains(normalizedCapability)) {
|
||||||
|
Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name(),
|
||||||
|
"Host binding '%s.%s' references unknown capability '%s'"
|
||||||
|
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
|
||||||
|
hostBinding.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var bindingKey = "%s|%s|%d".formatted(
|
||||||
|
hostBinding.abiModule(),
|
||||||
|
hostBinding.abiMethod(),
|
||||||
|
hostBinding.abiVersion());
|
||||||
|
final var firstBindingCapability = firstCapabilityByHostBinding.putIfAbsent(
|
||||||
|
bindingKey,
|
||||||
|
new FirstBindingCapability(normalizedCapability, hostBinding.span()));
|
||||||
|
if (firstBindingCapability != null && !firstBindingCapability.capability().equals(normalizedCapability)) {
|
||||||
|
Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_INCONSISTENT_BINDING_CAPABILITY.name(),
|
||||||
|
"Canonical host binding (%s, %s, %d) has inconsistent capability metadata ('%s' vs '%s')"
|
||||||
|
.formatted(
|
||||||
|
hostBinding.abiModule(),
|
||||||
|
hostBinding.abiMethod(),
|
||||||
|
hostBinding.abiVersion(),
|
||||||
|
firstBindingCapability.capability(),
|
||||||
|
normalizedCapability),
|
||||||
|
hostBinding.span(),
|
||||||
|
List.of(new RelatedSpan("First capability declaration for this binding is here", firstBindingCapability.span())));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.enforceDeclaredCapabilities() && !declaredCapabilities.contains(normalizedCapability)) {
|
||||||
|
Diagnostics.error(
|
||||||
|
diagnostics,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_CAPABILITY_NOT_DECLARED.name(),
|
||||||
|
"Capability '%s' required by host binding '%s.%s' is not declared in host admission context"
|
||||||
|
.formatted(normalizedCapability, hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||||
|
hostBinding.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredCapabilities.add(normalizedCapability);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReadOnlyList.wrap(requiredCapabilities.stream().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> normalizedSet(final ReadOnlyList<String> values) {
|
||||||
|
final var normalized = new HashSet<String>();
|
||||||
|
for (final var value : values) {
|
||||||
|
final var capability = normalize(value);
|
||||||
|
if (!capability.isBlank()) {
|
||||||
|
normalized.add(capability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalize(final String capability) {
|
||||||
|
return capability == null ? "" : capability.trim().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FirstBindingCapability(
|
||||||
|
String capability,
|
||||||
|
p.studio.compiler.source.Span span) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import p.studio.compiler.pbs.semantics.PbsBuiltinLayoutResolver;
|
|||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -15,6 +16,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
public final class PbsReservedMetadataExtractor {
|
public final class PbsReservedMetadataExtractor {
|
||||||
private static final String ATTR_HOST = "Host";
|
private static final String ATTR_HOST = "Host";
|
||||||
|
private static final String ATTR_CAPABILITY = "Capability";
|
||||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||||
@ -51,7 +53,8 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
return new IRReservedMetadata(
|
return new IRReservedMetadata(
|
||||||
ReadOnlyList.wrap(hostMethodBindings),
|
ReadOnlyList.wrap(hostMethodBindings),
|
||||||
ReadOnlyList.wrap(builtinTypeSurfaces),
|
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||||
ReadOnlyList.wrap(builtinConstSurfaces));
|
ReadOnlyList.wrap(builtinConstSurfaces),
|
||||||
|
requiredCapabilities(hostMethodBindings));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractHostBindings(
|
private void extractHostBindings(
|
||||||
@ -66,12 +69,18 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
final var abiModule = stringArgument(hostMetadata, "module").orElse("");
|
final var abiModule = stringArgument(hostMetadata, "module").orElse("");
|
||||||
final var abiMethod = stringArgument(hostMetadata, "name").orElse(signature.name());
|
final var abiMethod = stringArgument(hostMetadata, "name").orElse(signature.name());
|
||||||
final var abiVersion = longArgument(hostMetadata, "version").orElse(0L);
|
final var abiVersion = longArgument(hostMetadata, "version").orElse(0L);
|
||||||
|
final var capabilityAttribute = firstAttributeNamed(signature.attributes(), ATTR_CAPABILITY);
|
||||||
|
final var capability = capabilityAttribute
|
||||||
|
.flatMap(attr -> stringArgument(attr, "name"))
|
||||||
|
.orElse("");
|
||||||
hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding(
|
hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding(
|
||||||
hostDecl.name(),
|
hostDecl.name(),
|
||||||
signature.name(),
|
signature.name(),
|
||||||
abiModule,
|
abiModule,
|
||||||
abiMethod,
|
abiMethod,
|
||||||
abiVersion,
|
abiVersion,
|
||||||
|
capabilityAttribute.isPresent(),
|
||||||
|
capability,
|
||||||
signature.span()));
|
signature.span()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,4 +227,16 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
case ERROR -> "error";
|
case ERROR -> "error";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReadOnlyList<String> requiredCapabilities(
|
||||||
|
final List<IRReservedMetadata.HostMethodBinding> hostMethodBindings) {
|
||||||
|
final var required = new LinkedHashSet<String>();
|
||||||
|
for (final var hostBinding : hostMethodBindings) {
|
||||||
|
if (hostBinding.requiredCapability() == null || hostBinding.requiredCapability().isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
required.add(hostBinding.requiredCapability().trim().toLowerCase());
|
||||||
|
}
|
||||||
|
return ReadOnlyList.wrap(required.stream().toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,12 +16,14 @@ import java.util.Set;
|
|||||||
|
|
||||||
public final class PbsDeclarationSemanticsValidator {
|
public final class PbsDeclarationSemanticsValidator {
|
||||||
private static final String ATTR_HOST = "Host";
|
private static final String ATTR_HOST = "Host";
|
||||||
|
private static final String ATTR_CAPABILITY = "Capability";
|
||||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||||
|
|
||||||
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
||||||
ATTR_HOST,
|
ATTR_HOST,
|
||||||
|
ATTR_CAPABILITY,
|
||||||
ATTR_BUILTIN_TYPE,
|
ATTR_BUILTIN_TYPE,
|
||||||
ATTR_BUILTIN_CONST,
|
ATTR_BUILTIN_CONST,
|
||||||
ATTR_INTRINSIC_CALL);
|
ATTR_INTRINSIC_CALL);
|
||||||
@ -386,7 +388,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
if (!isReservedAttribute(attribute.name())) {
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ATTR_HOST.equals(attribute.name())) {
|
if (ATTR_HOST.equals(attribute.name()) || ATTR_CAPABILITY.equals(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reportInvalidReservedAttributeTarget(
|
reportInvalidReservedAttributeTarget(
|
||||||
|
|||||||
@ -145,7 +145,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
parsedSource.fileId(),
|
parsedSource.fileId(),
|
||||||
parsedSource.ast(),
|
parsedSource.ast(),
|
||||||
diagnostics,
|
diagnostics,
|
||||||
parsedSource.sourceKind());
|
parsedSource.sourceKind(),
|
||||||
|
ctx.hostAdmissionContext());
|
||||||
if (diagnostics.errorCount() > compileErrorBaseline) {
|
if (diagnostics.errorCount() > compileErrorBaseline) {
|
||||||
failedModuleKeys.add(parsedSource.moduleKey());
|
failedModuleKeys.add(parsedSource.moduleKey());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,5 +2,6 @@ import { Color } from @core:color;
|
|||||||
|
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,4 +103,29 @@ class PbsDiagnosticsContractTest {
|
|||||||
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
|
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
|
||||||
assertEquals(9, diagnostic.getSpan().getFileId().getId());
|
assertEquals(9, diagnostic.getSpan().getFileId().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTagHostAdmissionDiagnosticsWithHostAdmissionPhase() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(
|
||||||
|
new FileId(10),
|
||||||
|
source,
|
||||||
|
diagnostics,
|
||||||
|
p.studio.compiler.models.SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
final var diagnostic = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertEquals(DiagnosticPhase.HOST_ADMISSION, diagnostic.getPhase());
|
||||||
|
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
|
||||||
|
assertEquals(10, diagnostic.getSpan().getFileId().getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
package p.studio.compiler.pbs;
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.messages.HostAdmissionContext;
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.pbs.lexer.LexErrors;
|
import p.studio.compiler.pbs.lexer.LexErrors;
|
||||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@ -115,6 +118,7 @@ class PbsFrontendCompilerTest {
|
|||||||
}
|
}
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
[BuiltinConst(target = "Color", name = "white", version = 1)]
|
[BuiltinConst(target = "Color", name = "white", version = 1)]
|
||||||
@ -130,10 +134,102 @@ class PbsFrontendCompilerTest {
|
|||||||
assertEquals(1, fileBackend.reservedMetadata().hostMethodBindings().size());
|
assertEquals(1, fileBackend.reservedMetadata().hostMethodBindings().size());
|
||||||
assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().size());
|
assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().size());
|
||||||
assertEquals(1, fileBackend.reservedMetadata().builtinConstSurfaces().size());
|
assertEquals(1, fileBackend.reservedMetadata().builtinConstSurfaces().size());
|
||||||
|
assertEquals(1, fileBackend.reservedMetadata().requiredCapabilities().size());
|
||||||
|
assertEquals("gfx", fileBackend.reservedMetadata().requiredCapabilities().getFirst());
|
||||||
assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName());
|
assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName());
|
||||||
assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size());
|
assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectHostBindingWithoutCapabilityAtHostAdmission() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var compiler = new PbsFrontendCompiler();
|
||||||
|
final var fileBackend = compiler.compileFile(new FileId(7), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
final var hostDiagnostic = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
|
||||||
|
assertEquals(hostDiagnostic.getCode(), hostDiagnostic.getTemplateId());
|
||||||
|
assertEquals(0, fileBackend.functions().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectHostBindingCapabilityNotDeclaredInStrictContext() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var compiler = new PbsFrontendCompiler();
|
||||||
|
compiler.compileFile(
|
||||||
|
new FileId(8),
|
||||||
|
source,
|
||||||
|
diagnostics,
|
||||||
|
SourceKind.SDK_INTERFACE,
|
||||||
|
HostAdmissionContext.strictWithDeclaredCapabilities(ReadOnlyList.wrap(java.util.List.of("input"))));
|
||||||
|
|
||||||
|
final var hostDiagnostic = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_CAPABILITY_NOT_DECLARED.name()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectUnknownHostCapabilityAtHostAdmission() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "video")]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var compiler = new PbsFrontendCompiler();
|
||||||
|
compiler.compileFile(new FileId(9), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
final var hostDiagnostic = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectMalformedHostCapabilityMetadataAtHostAdmission() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(version = 1)]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var compiler = new PbsFrontendCompiler();
|
||||||
|
compiler.compileFile(new FileId(10), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
final var hostDiagnostic = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldInferBuiltinFieldSlotsFromDeclarationOrder() {
|
void shouldInferBuiltinFieldSlotsFromDeclarationOrder() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
@ -59,6 +59,7 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
|
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
@ -165,6 +166,7 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
|
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
@ -229,6 +231,7 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
|
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,6 +271,30 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
DiagnosticPhase.STATIC_SEMANTICS);
|
DiagnosticPhase.STATIC_SEMANTICS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void gateU_shouldEmitHostAdmissionDiagnosticForMissingCapabilityMetadata() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(
|
||||||
|
new FileId(42),
|
||||||
|
source,
|
||||||
|
diagnostics,
|
||||||
|
SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
final var hostAdmission = firstDiagnostic(diagnostics,
|
||||||
|
d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name()));
|
||||||
|
assertStableDiagnosticIdentity(
|
||||||
|
hostAdmission,
|
||||||
|
PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name(),
|
||||||
|
DiagnosticPhase.HOST_ADMISSION);
|
||||||
|
}
|
||||||
|
|
||||||
private WorkspaceCompileResult compileWorkspaceModule(
|
private WorkspaceCompileResult compileWorkspaceModule(
|
||||||
final Path projectRoot,
|
final Path projectRoot,
|
||||||
final String sourceContent,
|
final String sourceContent,
|
||||||
|
|||||||
@ -224,6 +224,7 @@ class PbsModuleVisibilityTest {
|
|||||||
"""
|
"""
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int) -> void;
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
|||||||
@ -127,6 +127,7 @@ class PbsParserTest {
|
|||||||
void shouldRejectAttributesInOrdinarySourceAndRecover() {
|
void shouldRejectAttributesInOrdinarySourceAndRecover() {
|
||||||
final var source = """
|
final var source = """
|
||||||
[Host(module = "gfx", name = "draw", version = 1)]
|
[Host(module = "gfx", name = "draw", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn run() -> int { return 1; }
|
fn run() -> int { return 1; }
|
||||||
declare struct Point(x: int,);
|
declare struct Point(x: int,);
|
||||||
""";
|
""";
|
||||||
@ -230,6 +231,7 @@ class PbsParserTest {
|
|||||||
}
|
}
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw(x: int, y: int, color: Color) -> void;
|
fn draw(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
[BuiltinConst(target = "Color", name = "white", version = 1)]
|
[BuiltinConst(target = "Color", name = "white", version = 1)]
|
||||||
@ -256,7 +258,7 @@ class PbsParserTest {
|
|||||||
|
|
||||||
final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1));
|
final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1));
|
||||||
assertEquals(1, hostDecl.signatures().size());
|
assertEquals(1, hostDecl.signatures().size());
|
||||||
assertEquals(1, hostDecl.signatures().getFirst().attributes().size());
|
assertEquals(2, hostDecl.signatures().getFirst().attributes().size());
|
||||||
|
|
||||||
final var constDecl = assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(2));
|
final var constDecl = assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(2));
|
||||||
assertEquals(1, constDecl.attributes().size());
|
assertEquals(1, constDecl.attributes().size());
|
||||||
@ -267,6 +269,7 @@ class PbsParserTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw(x: int, y: int) -> void;
|
fn draw(x: int, y: int) -> void;
|
||||||
unexpected_token
|
unexpected_token
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
|
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int, c: Color) -> void;
|
fn draw_pixel(x: int, y: int, c: Color) -> void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
|
|
||||||
declare contract Api {
|
declare contract Api {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn run() -> void;
|
fn run() -> void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -246,6 +246,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
}
|
}
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw(x: int, y: int, color: Color) -> void;
|
fn draw(x: int, y: int, color: Color) -> void;
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
@ -529,6 +530,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
"""
|
"""
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
[Capability(name = "gfx")]
|
||||||
fn draw_pixel(x: int, y: int) -> void;
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
}
|
}
|
||||||
"""))),
|
"""))),
|
||||||
|
|||||||
@ -11,12 +11,13 @@ public class FrontendPhaseContext {
|
|||||||
public final FileTableReader fileTable;
|
public final FileTableReader fileTable;
|
||||||
public final BuildStack stack;
|
public final BuildStack stack;
|
||||||
private final int stdlibVersion;
|
private final int stdlibVersion;
|
||||||
|
private final HostAdmissionContext hostAdmissionContext;
|
||||||
|
|
||||||
public FrontendPhaseContext(
|
public FrontendPhaseContext(
|
||||||
final ProjectTableReader projectTable,
|
final ProjectTableReader projectTable,
|
||||||
final FileTableReader fileTable,
|
final FileTableReader fileTable,
|
||||||
final BuildStack stack) {
|
final BuildStack stack) {
|
||||||
this(projectTable, fileTable, stack, 1);
|
this(projectTable, fileTable, stack, 1, HostAdmissionContext.permissiveDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrontendPhaseContext(
|
public FrontendPhaseContext(
|
||||||
@ -24,10 +25,22 @@ public class FrontendPhaseContext {
|
|||||||
final FileTableReader fileTable,
|
final FileTableReader fileTable,
|
||||||
final BuildStack stack,
|
final BuildStack stack,
|
||||||
final int stdlibVersion) {
|
final int stdlibVersion) {
|
||||||
|
this(projectTable, fileTable, stack, stdlibVersion, HostAdmissionContext.permissiveDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrontendPhaseContext(
|
||||||
|
final ProjectTableReader projectTable,
|
||||||
|
final FileTableReader fileTable,
|
||||||
|
final BuildStack stack,
|
||||||
|
final int stdlibVersion,
|
||||||
|
final HostAdmissionContext hostAdmissionContext) {
|
||||||
this.projectTable = projectTable;
|
this.projectTable = projectTable;
|
||||||
this.fileTable = fileTable;
|
this.fileTable = fileTable;
|
||||||
this.stack = stack;
|
this.stack = stack;
|
||||||
this.stdlibVersion = stdlibVersion;
|
this.stdlibVersion = stdlibVersion;
|
||||||
|
this.hostAdmissionContext = hostAdmissionContext == null
|
||||||
|
? HostAdmissionContext.permissiveDefault()
|
||||||
|
: hostAdmissionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceKind sourceKind(final ProjectId projectId) {
|
public SourceKind sourceKind(final ProjectId projectId) {
|
||||||
@ -37,4 +50,8 @@ public class FrontendPhaseContext {
|
|||||||
public int stdlibVersion() {
|
public int stdlibVersion() {
|
||||||
return stdlibVersion;
|
return stdlibVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HostAdmissionContext hostAdmissionContext() {
|
||||||
|
return hostAdmissionContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
package p.studio.compiler.messages;
|
||||||
|
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public record HostAdmissionContext(
|
||||||
|
ReadOnlyList<String> knownCapabilities,
|
||||||
|
ReadOnlyList<String> declaredCapabilities,
|
||||||
|
boolean enforceDeclaredCapabilities) {
|
||||||
|
|
||||||
|
private static final ReadOnlyList<String> DEFAULT_KNOWN_CAPABILITIES = ReadOnlyList.wrap(List.of(
|
||||||
|
"system",
|
||||||
|
"gfx",
|
||||||
|
"input",
|
||||||
|
"audio",
|
||||||
|
"fs",
|
||||||
|
"log",
|
||||||
|
"asset",
|
||||||
|
"bank"));
|
||||||
|
|
||||||
|
public HostAdmissionContext {
|
||||||
|
knownCapabilities = knownCapabilities == null || knownCapabilities.isEmpty()
|
||||||
|
? DEFAULT_KNOWN_CAPABILITIES
|
||||||
|
: normalize(knownCapabilities);
|
||||||
|
declaredCapabilities = declaredCapabilities == null
|
||||||
|
? ReadOnlyList.empty()
|
||||||
|
: normalize(declaredCapabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HostAdmissionContext permissiveDefault() {
|
||||||
|
return new HostAdmissionContext(DEFAULT_KNOWN_CAPABILITIES, ReadOnlyList.empty(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HostAdmissionContext strictWithDeclaredCapabilities(
|
||||||
|
final ReadOnlyList<String> declaredCapabilities) {
|
||||||
|
return new HostAdmissionContext(DEFAULT_KNOWN_CAPABILITIES, declaredCapabilities, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReadOnlyList<String> normalize(final ReadOnlyList<String> values) {
|
||||||
|
final Set<String> dedup = new LinkedHashSet<>();
|
||||||
|
for (final var value : values) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dedup.add(value.trim().toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
return ReadOnlyList.wrap(dedup.stream().toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import lombok.Getter;
|
|||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
@Getter
|
@Getter
|
||||||
@ -23,6 +24,7 @@ public class IRBackend {
|
|||||||
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
|
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
|
||||||
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
|
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
|
||||||
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
|
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
|
||||||
|
private final LinkedHashSet<String> requiredCapabilities = new LinkedHashSet<>();
|
||||||
|
|
||||||
public void merge(final IRBackendFile backendFile) {
|
public void merge(final IRBackendFile backendFile) {
|
||||||
if (backendFile == null) {
|
if (backendFile == null) {
|
||||||
@ -33,6 +35,7 @@ public class IRBackend {
|
|||||||
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
|
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
|
||||||
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
|
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
|
||||||
builtinConstSurfaces.addAll(metadata.builtinConstSurfaces().asList());
|
builtinConstSurfaces.addAll(metadata.builtinConstSurfaces().asList());
|
||||||
|
requiredCapabilities.addAll(metadata.requiredCapabilities().asList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRBackend emit() {
|
public IRBackend emit() {
|
||||||
@ -42,7 +45,8 @@ public class IRBackend {
|
|||||||
.reservedMetadata(new IRReservedMetadata(
|
.reservedMetadata(new IRReservedMetadata(
|
||||||
ReadOnlyList.wrap(hostMethodBindings),
|
ReadOnlyList.wrap(hostMethodBindings),
|
||||||
ReadOnlyList.wrap(builtinTypeSurfaces),
|
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||||
ReadOnlyList.wrap(builtinConstSurfaces)))
|
ReadOnlyList.wrap(builtinConstSurfaces),
|
||||||
|
ReadOnlyList.wrap(requiredCapabilities.stream().toList())))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,6 +58,7 @@ public class IRBackend {
|
|||||||
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
|
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
|
||||||
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
|
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
|
||||||
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
|
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
|
||||||
|
.append(", requiredCapabilities=").append(reservedMetadata.requiredCapabilities().size())
|
||||||
.append('}');
|
.append('}');
|
||||||
|
|
||||||
if (functions.isEmpty()) {
|
if (functions.isEmpty()) {
|
||||||
|
|||||||
@ -8,16 +8,22 @@ import java.util.Objects;
|
|||||||
public record IRReservedMetadata(
|
public record IRReservedMetadata(
|
||||||
ReadOnlyList<HostMethodBinding> hostMethodBindings,
|
ReadOnlyList<HostMethodBinding> hostMethodBindings,
|
||||||
ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces,
|
ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces,
|
||||||
ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces) {
|
ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces,
|
||||||
|
ReadOnlyList<String> requiredCapabilities) {
|
||||||
|
|
||||||
public IRReservedMetadata {
|
public IRReservedMetadata {
|
||||||
hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings;
|
hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings;
|
||||||
builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces;
|
builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces;
|
||||||
builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces;
|
builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces;
|
||||||
|
requiredCapabilities = requiredCapabilities == null ? ReadOnlyList.empty() : requiredCapabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IRReservedMetadata empty() {
|
public static IRReservedMetadata empty() {
|
||||||
return new IRReservedMetadata(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty());
|
return new IRReservedMetadata(
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public record HostMethodBinding(
|
public record HostMethodBinding(
|
||||||
@ -26,12 +32,15 @@ public record IRReservedMetadata(
|
|||||||
String abiModule,
|
String abiModule,
|
||||||
String abiMethod,
|
String abiMethod,
|
||||||
long abiVersion,
|
long abiVersion,
|
||||||
|
boolean capabilityDeclared,
|
||||||
|
String requiredCapability,
|
||||||
Span span) {
|
Span span) {
|
||||||
public HostMethodBinding {
|
public HostMethodBinding {
|
||||||
ownerName = Objects.requireNonNull(ownerName, "ownerName");
|
ownerName = Objects.requireNonNull(ownerName, "ownerName");
|
||||||
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
|
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
|
||||||
abiModule = Objects.requireNonNull(abiModule, "abiModule");
|
abiModule = Objects.requireNonNull(abiModule, "abiModule");
|
||||||
abiMethod = Objects.requireNonNull(abiMethod, "abiMethod");
|
abiMethod = Objects.requireNonNull(abiMethod, "abiMethod");
|
||||||
|
requiredCapability = requiredCapability == null ? "" : requiredCapability;
|
||||||
span = Objects.requireNonNull(span, "span");
|
span = Objects.requireNonNull(span, "span");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user