From aa1f45486baae4564e327ed74f7fce47357be5aa Mon Sep 17 00:00:00 2001 From: Q Date: Thu, 13 Jan 2022 21:15:41 +0100 Subject: [PATCH] Allow capabilities to be referenced before they're defined --- source/crochet/crochet.ts | 1 + source/vm/boot.ts | 16 ++++++- source/vm/intrinsics.ts | 2 + source/vm/primitives/capability.ts | 66 +++++++++++++++++++++++++++-- tests/vm-tests/capabilities.crochet | 7 +++ tests/vm-tests/crochet.json | 3 +- 6 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/vm-tests/capabilities.crochet diff --git a/source/crochet/crochet.ts b/source/crochet/crochet.ts index 62dc7166..ad0b2405 100644 --- a/source/crochet/crochet.ts +++ b/source/crochet/crochet.ts @@ -228,6 +228,7 @@ export class BootedCrochet { VM.Types.verify_package_types(cpkg); VM.Types.verify_package_traits(cpkg); + VM.Capability.verify_package_capabilities(cpkg); } private reify_capability_grants(pkg: Package.ResolvedPackage) { diff --git a/source/vm/boot.ts b/source/vm/boot.ts index b81eaec7..92aa3bbf 100644 --- a/source/vm/boot.ts +++ b/source/vm/boot.ts @@ -721,12 +721,26 @@ export function load_declaration( } case t.CAPABILITY: { - const capability = new CrochetCapability( + const new_capability = new CrochetCapability( module, declaration.name, declaration.documentation, declaration.meta ); + let capability; + const missing = Capability.try_get_placeholder_capability( + module, + declaration.name + ); + if (missing != null) { + capability = Capability.fulfill_placeholder_capability( + module, + missing, + new_capability + ); + } else { + capability = new_capability; + } Capability.define_capability(module, capability); break; } diff --git a/source/vm/intrinsics.ts b/source/vm/intrinsics.ts index 3d6d4003..ef99ae22 100644 --- a/source/vm/intrinsics.ts +++ b/source/vm/intrinsics.ts @@ -364,6 +364,7 @@ export class CrochetWorld { export class CrochetPackage { readonly missing_traits: Namespace; readonly missing_types: Namespace; + readonly missing_capabilities: Namespace; readonly types: PassthroughNamespace; readonly traits: PassthroughNamespace; readonly definitions: PassthroughNamespace; @@ -382,6 +383,7 @@ export class CrochetPackage { ) { this.missing_traits = new Namespace(null, null, null); this.missing_types = new Namespace(null, null, null); + this.missing_capabilities = new Namespace(null, null, null); this.types = new PassthroughNamespace(world.types, name); this.traits = new PassthroughNamespace(world.traits, name); this.definitions = new PassthroughNamespace(world.definitions, name); diff --git a/source/vm/primitives/capability.ts b/source/vm/primitives/capability.ts index d628fa7b..a0cd627d 100644 --- a/source/vm/primitives/capability.ts +++ b/source/vm/primitives/capability.ts @@ -1,6 +1,7 @@ import { CrochetCapability, CrochetModule, + CrochetPackage, CrochetTrait, CrochetType, CrochetValue, @@ -31,13 +32,70 @@ export function define_capability( export function get_capability(module: CrochetModule, name: string) { const capability = module.pkg.capabilities.try_lookup(name); - if (capability == null) { + if (capability != null) { + return capability; + } + + const missing = module.pkg.missing_capabilities.try_lookup(name); + if (missing != null) { + return missing; + } + + const placeholder = make_placeholder_capability(module, name); + module.pkg.missing_capabilities.define(name, placeholder); + return placeholder; +} + +export function make_placeholder_capability( + module: CrochetModule, + name: string +) { + return new CrochetCapability(module, name, "(placeholder capability)", null); +} + +export function try_get_placeholder_capability( + module: CrochetModule, + name: string +) { + return module.pkg.missing_capabilities.try_lookup(name); +} + +export function fulfill_placeholder_capability( + module: CrochetModule, + placeholder: CrochetCapability, + capability: CrochetCapability +) { + const value = module.pkg.missing_capabilities.try_lookup(capability.name); + if (value !== placeholder) { + throw new ErrArbitrary( + "internal", + `Invalid placeholder for ${capability.name}` + ); + } + + module.pkg.missing_capabilities.remove(capability.name); + const p: { -readonly [k in keyof typeof capability]: typeof capability[k] } = + placeholder; + p.protecting = capability.protecting; + p.module = capability.module; + p.name = capability.name; + p.documentation = capability.documentation; + p.meta = capability.meta; + + return p; +} + +export function verify_package_capabilities(pkg: CrochetPackage) { + if (pkg.missing_capabilities.own_bindings.size > 0) { throw new ErrArbitrary( - "undefined-capability", - `The capability ${name} is not defined in package ${module.pkg.name}` + "internal", + `Package ${ + pkg.name + } cannot be loaded because it's missing the following capability definitions: ${[ + ...pkg.missing_capabilities.own_bindings.keys(), + ].join(", ")}` ); } - return capability; } export function protect_type( diff --git a/tests/vm-tests/capabilities.crochet b/tests/vm-tests/capabilities.crochet new file mode 100644 index 00000000..cdf78dc4 --- /dev/null +++ b/tests/vm-tests/capabilities.crochet @@ -0,0 +1,7 @@ +% crochet + +// We can refer to capabilities before they're defined +type capability-protected; +protect type capability-protected with protecc; + +capability protecc; diff --git a/tests/vm-tests/crochet.json b/tests/vm-tests/crochet.json index a2bd5d25..7bfd7a71 100644 --- a/tests/vm-tests/crochet.json +++ b/tests/vm-tests/crochet.json @@ -30,7 +30,8 @@ "actions.crochet", "effects.crochet", "dsl.crochet", - "traits.crochet" + "traits.crochet", + "capabilities.crochet" ], "capabilities": { "requires": ["crochet.debug/internal"],