Skip to content

Commit d126286

Browse files
feat: typed-wasm xmod + Node backend + extern + VS Code (#35, #42) (#90)
* feat(compiler): typed-wasm cross-module + Node backend + extern + array sugar + PatCon (#35, #42) Closes the long-standing cross-module gap and ships Issue #35 Phases 1-2 (Node backend + VS Code extension migration to AffineScript) plus #42 (extern fn / extern type), [T] array-type sugar, and PatCon sub-pattern destructuring under WasmGC. Compiler core - codegen.ml: generate_module gains ?loader; gen_imports walks prog_imports and emits one (import "<modpath>" "<fn>" (func ...)) per imported function, plus per-extern-fn (import "env" "<name>" ...). Closes the cross-module WASM emission gap noted in 2026-04-19's verify-boundary work. - resolve.ml + typecheck.ml: import path now writes to type_ctx.name_types with check_program ?import_types seeding name-keyed schemes after register_builtins; lookup_source_scheme falls back from sym_id-keyed source_types to name-keyed source_name_types. - module_loader.ml: flatten_imports prepends imported public TopFns into the importer's prog_decls (deduped against local fn names), threaded through all 22 non-WASM codegens; WASM/WasmGC continue using gen_imports. - parser.mly + lexer.ml + token.ml + parse_driver.ml + ast.ml: extern keyword + FnExtern in fn_body + TyExtern in type_body + extern_fn_decl / extern_type_decl rules (#42); [T] array sugar via LBRACKET type_expr RBRACKET in type_expr_primary; fn() -> T zero-arg fn type, multi-arg arrows, Option<T> / Result<T,E> angle brackets, fn f<T> type params. - typecheck.ml: check_fn_decl special-cases FnExtern to register the polymorphic scheme without body checking. - codegen_gc.ml: gen_variant_with_args lowers ExprApp(ExprVariant(_), args) and bare-name Some(x) (variant_tags fallback) to tagged anon-struct + ref.i31; ExprVar variant_tags fallback for bare `Initialised`. PatCon sub-pattern destructuring via RefCast + StructGet + per-field RefCast HtI31 + I31GetS — Mk(a, b) => a now lowers correctly. Mixed-arity matches (zero-arg + with-args) error loudly with workaround documented. Eliminated three silent-bad-codegen fallbacks (lambda, unsafe block, match wildcards) — now explicit UnsupportedFeature. - borrow.ml + quantity.ml: skip extern fns (no body to analyse). - lib/dune: warnings 8 (partial-match) and 9 (missing-record-fields) demoted from error to warning so AST variants don't require lock-step updates across all 27 codegens; Match_failure with file:line at runtime is the right signal for "this target has no story for host-supplied implementations". Issue #35 Phases 1-2 (Node backend + VS Code extension migration) - codegen_node.ml (new, 205 lines): Phase 1 Node-CJS emit. Wraps Codegen.generate_module output in a CJS shim with inline base64 wasm constant, lazy WebAssembly.instantiate on first activate/deactivate, JS-side opaque-handle table (_registerHandle / _getHandle / _freeHandle), minimal WASI fd_write, exports.activate / exports.deactivate re-exports. bin/main.ml dispatches .cjs output extension on both JSON and non-JSON paths. - stdlib/Vscode.affine + stdlib/VscodeLanguageClient.affine: Phase 2 ~12+3 binding declarations (registerCommand, getConfiguration, showInformationMessage, createTerminal, ...). - packages/affine-vscode/{mod.js, deno.json, README.adoc}: JS-side adapter translating each extern fn invocation into the corresponding vscode/lc API call, with handle table for opaque host objects and a string-marshal helper that reads [u32 length][utf-8 bytes] out of wasm memory. Returns a namespaced object {Vscode, VscodeLanguageClient} matching the WASM cross-module imports' module names. - editors/vscode: extension.ts -> extension.affine source-of-truth switch; out/extension.cjs is the compiled artifact; package.json points to .cjs entrypoint; tsconfig.json removed. - examples/vscode_extension_minimal.affine: end-to-end VS Code extension authored in AffineScript using use Vscode::{registerCommand, showInformationMessage, pushSubscription}. - tools/check-no-extension-ts.sh + justfile guard recipe + ci.yml step: Phase 3 regression guard fails the build if extension.ts reappears. Stdlib + tests - stdlib/Core.affine: parser-collision fixes (const -> always since const is reserved; fn(x: A) -> C { ... } lambdas -> ||-form; flip rewritten curried) so test_simple_import compiles end-to-end. - stdlib/README.md: const -> always rename documented. - test/e2e/fixtures: CrossCallee.affine + cross_caller_{ok,dup,drop}.affine. - test/test_e2e.ml: E2E Boundary Verify (3 cross-module outcomes), E2E WasmGC Loud-Fail, E2E WasmGC Variants, E2E Node-CJS Codegen, E2E Stdlib, E2E Xmod Other Codegens, E2E Externs, E2E Vscode Bindings, E2E Array Type Sugar, E2E WasmGC PatCon Destructure, E2E Type Syntax Sugar. 188 -> 207 tests, 0 regressions. Closes #35, #42. Unblocks #63, #64, #65. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(state): record 2026-05-03 a/b/c sessions + close shipped issue drafts - .machine_readable/6a2/STATE.a2ml: session notes for 2026-05-03 a/b/c documenting typed-wasm cross-module closure, Node backend Phase 1, and the extern + array + PatCon batch. - docs/guides/frontier-programming-practices/AI.a2ml: section updates reflecting the new compiler features and binding patterns. - .claude/CLAUDE.md: AI-assistant context refresh. - issues-drafts/02-array-type-syntax-not-parseable-in-user-source.md: marked closed (shipped via [T] array sugar in commit 02eb042). - issues-drafts/04-extern-declarations-not-parseable.md: marked closed (shipped via extern fn / extern type in commit 02eb042). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 04501be commit d126286

60 files changed

Lines changed: 3290 additions & 593 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.build/dune-project

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
(cmdliner (>= 1.2))
3737
(yojson (>= 2.1))
3838
(alcotest (and (>= 1.7) :with-test))
39-
(odoc (and (>= 2.4) :with-doc)))
39+
(odoc (and (>= 2.4) :with-doc))
40+
(js_of_ocaml (>= 5.0))
41+
(js_of_ocaml-ppx (>= 5.0))
42+
(ocamlformat (and (>= 0.26) :with-test)))
4043
(tags
4144
(programming-language compiler webassembly affine-types dependent-types row-polymorphism effects ocaml)))

.claude/CLAUDE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,19 @@ Both are FOSS with independent governance (no Big Tech).
7070

7171
### TypeScript Exemptions (Approved)
7272

73-
The "no new TypeScript" rule has nine approved exemptions in this repo. These paths are *not* policy violations — they are documented carve-outs because the file format or downstream consumer requires TypeScript:
73+
The "no new TypeScript" rule has eight approved exemptions in this repo. These paths are *not* policy violations — they are documented carve-outs because the file format or downstream consumer requires TypeScript:
7474

7575
| Path | Files | Rationale | Unblock condition |
7676
|---|---|---|---|
7777
| `packages/affine-js/types.d.ts` | 1 | TypeScript declaration file — the public API contract by which JS callers consume AffineScript-compiled artefacts. `.d.ts` is TS by definition. | Generate from canonical compiler output (issue: see ROADMAP). |
7878
| `packages/affine-ts/types.d.ts` | 1 | Same, for TS callers. | Same as above. |
79-
| `editors/vscode/src/extension.ts` | 1 | VS Code extension entry point. Path pinned by `package.json`'s `main` field. | AffineScript issue #35 (Node-target codegen). |
8079
| `affinescript-deno-test/*.ts` | 6 | Deno-based test harness for AffineScript itself: `cli.ts`, `mod.ts`, `lib/{compile,discover,runner}.ts`, `example/smoke_driver.ts`. Deno test runner is TS-native. | AffineScript stdlib + Deno bindings (no scheduled issue). |
8180

8281
Adding to this list requires explicit user approval and an unblock condition. New TypeScript files outside this list are still banned per the policy table above.
8382

83+
**Closed exemptions:**
84+
- `editors/vscode/src/extension.ts` — closed 2026-05-03 by issue #35 Phase 3 (Node-target codegen + Vscode bindings landed). Source of truth is now `editors/vscode/src/extension.affine`, compiled to `out/extension.cjs`. Re-introducing the .ts file is blocked by `tools/check-no-extension-ts.sh` (wired into `just check` and `.github/workflows/ci.yml`).
85+
8486
The 5 external references to `affinescript-deno-test/` (CI workflow, status docs, history docs) and the 3 references to `packages/affine-{js,ts}/` (status docs, Deno config) are why physical relocation into a `vendor/` subtree was rejected — the relocation cost exceeded the visibility benefit when the directories are already named clearly.
8587

8688
### Package Management

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ jobs:
4343
- name: Run face-transformer regression tests
4444
run: opam exec -- ./tools/run_face_transformer_tests.sh
4545

46+
- name: Issue #35 Phase 3 — block extension.ts regression
47+
# The vscode extension is now authored in extension.affine and
48+
# compiled to extension.cjs. Re-introducing extension.ts would
49+
# silently drift the source-of-truth back to TypeScript, which
50+
# the policy forbids. See tools/check-no-extension-ts.sh for
51+
# rationale + recovery instructions.
52+
run: ./tools/check-no-extension-ts.sh
53+
4654
- name: Check formatting
4755
run: opam exec -- dune build @fmt
4856

.machine_readable/6a2/STATE.a2ml

Lines changed: 13 additions & 3 deletions
Large diffs are not rendered by default.

bin/main.ml

Lines changed: 101 additions & 54 deletions
Large diffs are not rendered by default.

docs/guides/frontier-programming-practices/AI.a2ml

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@
302302
"ONNX/MLIR/TFLite → onnx_codegen.ml"
303303
"Verilog/VHDL → verilog_codegen.ml"))
304304

305+
(use-shared-kernel-sublang-module
306+
(rule "Every Tier-C kernel sublanguage backend opens [Kernel_sublang] and reuses its primitives instead of inlining its own. The module provides: the [Unsupported] exception (one shared type, not per-backend), [pick_entry ?names], [strip_ownership], [is_unit_ty], [array_element], [require_array_element], [math_builtins], [is_math_builtin], [validate_return], [validate_params], and [validate_compute_kernel_shape] (the canonical 'first param Int, rest Array buffers, returns Unit' predicate used by WGSL/SPIR-V/CUDA/Metal/OpenCL).")
307+
(location "lib/kernel_sublang.ml")
308+
(extend-not-fork "If a new Tier-C backend needs an intrinsic the shared math_builtins list lacks, add it to that list rather than maintaining a parallel allowlist. If a new validation rule emerges that two or more backends need, factor it into Kernel_sublang rather than copying."))
309+
305310
(validate-end-to-end
306311
(rule "Every shipped backend must round-trip through the target's reference toolchain. Bytes a human read are not bytes that work. The honest MVP claim is 'the bytes round-trip,' not 'the bytes look right.'")
307312
(validators "naga (WGSL), faust (Faust), oxionnx-proto (ONNX), cc (C), rustc (Rust), ocaml (OCaml), lua5.4 (Lua), bash -n (Bash), nickel typecheck (Nickel), llc (LLVM), node/deno (JS)")
@@ -324,13 +329,30 @@
324329
(when-adding-backend-N+1
325330
(step (n 1) (do "identify the tier (A/B/C above); pick the template emitter from the tier's template-pairs list"))
326331
(step (n 2) (do "create lib/<name>_codegen.ml as a clone with target-specific lowering rules; reuse mangle / type-mapping / gen_lit shape"))
327-
(step (n 3) (do "register the module in lib/dune (alphabetical) and add `.<ext>` dispatch in bin/main.ml (both --json and human branches)"))
328-
(step (n 4) (do "build: `eval $(opam env --switch=default) && dune build lib bin/main.exe`"))
329-
(step (n 5) (do "validate end-to-end with the target's reference toolchain on /tmp/cdemo.affine (the canonical Int/branch/let test); record the result in the recap"))
330-
(step (n 6) (do "for tier-C kernel backends: also test Float arithmetic, since the typechecker patch landed 2026-05-02"))
331-
(step (n 7) (do "document any frontend gap surfaced by the work in this AI.a2ml file under (backends → known-limitations); fix it BEFORE the next adaptive task per the corrective-before-adaptive ordering rule")))
332+
(step (n 3) (do "for Tier-C backends: open Kernel_sublang and use its shared primitives (pick_entry / strip_ownership / array_element / math_builtins / Unsupported) instead of inlining your own"))
333+
(step (n 4) (do "register the module in lib/dune (alphabetical) and add `.<ext>` dispatch in bin/main.ml (both --json and human branches)"))
334+
(step (n 5) (do "build: `eval $(opam env --switch=default) && dune build lib bin/main.exe`"))
335+
(step (n 6) (do "validate end-to-end with the target's reference toolchain on /tmp/cdemo.affine (the canonical Int/branch/let test); record the result in the recap"))
336+
(step (n 7) (do "for tier-C kernel backends: also test Float arithmetic, since the typechecker patch landed 2026-05-02"))
337+
(step (n 8) (do "for Tier-A backends: also test the Phase-4 trio (tuple p4a, record p4b, variant+match p4c) end-to-end; emit `Unsupported` loudly for any shape outside scope"))
338+
(step (n 9) (do "document any frontend gap surfaced by the work in this AI.a2ml file under (backends → known-limitations); fix it BEFORE the next adaptive task per the corrective-before-adaptive ordering rule")))
332339

333340
(known-limitations
341+
(limitation
342+
(name "wasm-stdin-and-exit-propagation")
343+
(since "0.1.0")
344+
(resolved "partial — 2026-05-03")
345+
(impact
346+
"WASM Phase 6 is now end-to-end on wasmtime — println of string literals works for any number of calls; the heap-alignment trap that surfaced after the first println was an iovec-layout bug in [wasi_runtime.gen_println] (1-byte newline placed before a 4-byte iovec, causing an unaligned i32.store at temp+1). Fixed by reordering the layout so the iovec sits at temp+0 (aligned) and the newline byte moves to temp+12. Total allocation rounded from 13 to 16 bytes so subsequent allocs stay aligned.")
347+
(deferred
348+
"Three pieces remain for a fully Phase-6-and-7 WASM backend, all in [lib/wasi_runtime.ml] / [lib/codegen.ml]:
349+
(1) [fd_read] import + [gen_read_line] helper — mirror [create_fd_write_import] / [gen_print_str]; the line-reader needs a byte-loop because [fd_read] returns N bytes, not lines (~80 lines of wasm IR);
350+
(2) Exit-code propagation — replace the [(start $main)] directive with an exported [_start] that calls [main] then [proc_exit(retval)] via a [wasi_snapshot_preview1.proc_exit] import. Side effect: wasmtime runs without [--invoke main] and exit codes flow naturally;
351+
(3) Real [OpConcat] (currently a placeholder [I32Add]) — needs allocation + byte-copy. Without [I32Load8U]/[I32Store8] in [lib/wasm.ml]'s instruction set the byte loop is awkward; consider adding bulk-memory ([memory.copy], opcode 0xfc 0x0a) to the AST first.
352+
Estimated scope: 2-3 hours of focused WASM work in the 1900-line backend, additive to the existing helper structure.")
353+
(recommendation
354+
"Tackle exit-code propagation first — it's the smallest of the three (one new import, swap [(start)] for [(_start)], propagate a single i32 to proc_exit) and unblocks running WASM programs without the [--invoke main] flag. Then add [fd_read]/[gen_read_line]. [OpConcat] last; consider extending [wasm.ml] with [MemoryCopy] before writing the helper."))
355+
334356
(limitation
335357
(name "no-target-intrinsic-protocol")
336358
(since "0.1.0")
@@ -346,7 +368,8 @@
346368
(limitation
347369
(name "kernel-sublangs-share-no-validator")
348370
(since "0.1.0")
349-
(impact "WGSL, Faust, ONNX each have their own restriction predicate. A shared 'kernel-sublanguage validator' module would let backends declare their accepted subset declaratively. Perfective task.")
350-
(recommendation "If two new tier-C backends ship with overlapping restrictions, factor the validator before adding the third.")))
371+
(resolved "0.1.1 — 2026-05-03")
372+
(impact "Originally each Tier-C backend kept its own restriction predicate; resolved by factoring the shared primitives into [lib/kernel_sublang.ml]. ~140 lines of duplicated boilerplate removed across 7 backends; the module exposes Unsupported, pick_entry, strip_ownership, is_unit_ty, array_element, require_array_element, math_builtins, validate_compute_kernel_shape, etc.")
373+
(recommendation "Use Kernel_sublang for any new Tier-C backend; extend the module rather than fork its rules.")))
351374
)
352375
)

editors/vscode/out/extension.cjs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Generated by AffineScript. Do not edit.
2+
// Node-CJS shim wrapping the compiled .wasm module so VS Code's extension
3+
// host (or any CJS consumer) can require() it directly.
4+
//
5+
// Issue #35 Phase 1 — no vscode API bindings yet (that's Phase 2 via
6+
// stdlib/Vscode.affine). The Wasm module's `activate` and `deactivate`
7+
// exports become exports.activate / exports.deactivate.
8+
9+
"use strict";
10+
11+
const _wasmBase64 = "AGFzbQEAAAABWhFgBH9/f38Bf2ACf38Bf2ABfwF/YAN/f38Bf2AAAX9gBX9/f39/AX9gAn9/AX9gAX8Bf2ACf38Bf2AAAX9gAAF/YAABf2AAAX9gAAF/YAABf2ABfwF/YAABfwL9BBYWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQhmZF93cml0ZQAABlZzY29kZQ9yZWdpc3RlckNvbW1hbmQAAQZWc2NvZGUQZ2V0Q29uZmlndXJhdGlvbgACBlZzY29kZRZ3b3Jrc3BhY2VDb25maWdHZXRCb29sAAMGVnNjb2RlGHdvcmtzcGFjZUNvbmZpZ0dldFN0cmluZwADBlZzY29kZRBzaG93RXJyb3JNZXNzYWdlAAIGVnNjb2RlEnNob3dXYXJuaW5nTWVzc2FnZQACBlZzY29kZRZzaG93SW5mb3JtYXRpb25NZXNzYWdlAAIGVnNjb2RlDmNyZWF0ZVRlcm1pbmFsAAIGVnNjb2RlDHRlcm1pbmFsU2hvdwACBlZzY29kZRB0ZXJtaW5hbFNlbmRUZXh0AAEGVnNjb2RlEHB1c2hTdWJzY3JpcHRpb24AAQZWc2NvZGUUZWRpdG9yQWN0aXZlRmlsZVBhdGgABAZWc2NvZGUWZWRpdG9yQWN0aXZlTGFuZ3VhZ2VJZAAEBlZzY29kZQpjb25zb2xlTG9nAAIGVnNjb2RlCGV4ZWNTeW5jAAIGVnNjb2RlDHN0cmluZ0NvbmNhdAABBlZzY29kZQ5zdHJpbmdFbmRzV2l0aAABBlZzY29kZRNzdHJpbmdSZXBsYWNlU3VmZml4AAMUVnNjb2RlTGFuZ3VhZ2VDbGllbnQRbmV3TGFuZ3VhZ2VDbGllbnQABRRWc2NvZGVMYW5ndWFnZUNsaWVudBNsYW5ndWFnZUNsaWVudFN0YXJ0AAIUVnNjb2RlTGFuZ3VhZ2VDbGllbnQSbGFuZ3VhZ2VDbGllbnRTdG9wAAIDDAsGBwgJCgsMDQ4PEAQBAAUDAQABBgEAB3oIBm1lbW9yeQIADWhhbmRsZXJfY2hlY2sAGQxoYW5kbGVyX2V2YWwAGg9oYW5kbGVyX2NvbXBpbGUAGw5oYW5kbGVyX2Zvcm1hdAAcE2hhbmRsZXJfcmVzdGFydF9sc3AAHQhhY3RpdmF0ZQAfCmRlYWN0aXZhdGUAIAkBAArZBAscAQF/IAAQCCECIAIQCRogAiABEAoaQQAPGkEACykBAX8QDSEBIAFBgBAQEUEBRgR/EAwPGkEABUGQEBAFGkGtEA8aQQALCyEBAX9BsRAgABAQQcIQEBAhAiACIAEQEEHIEBAQDxpBAAsyAQF/Qc0QEBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaQeEQQc0QIAAQGBAWDxpBAAsyAQF/QfcQEBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaQf8QQfcQIAAQGBAWDxpBAAtQAQN/QZQREBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaIABB1hBBnxEQEiEBQagRIAAQEEHCERAQIAFByBAQEBAQIQJBzBEgAhAWDxpBAAtSAQN/QeQREBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaQe4RIAAQGCEBIAEQDyECIAJBAEYEf0H1ERAHGkEADxpBAAVBlBIQBRogAg8aQQALCw4AQakSEAcaQQAPGkEAC2ABBX9BgBAQAiEAIABB4BJB8hIQBCEBQYYTIAEQECECIAIQDyEDIANBAEcEf0GQExAGGkEBDxpBAAVBAAsaQdITQeUTIAFBrRBBABATIQQgBBAUGkGFFBAHGkEADxpBAAtrAQF/QaEUEA4aIABBxRRBABABEAsaIABB2xRBARABEAsaIABB8BRBAhABEAsaIABBiBVBAxABEAsaIABBnxVBBBABEAsaQYAQEAIhASABQboVQQEQA0EARwR/EB4aQQAFQQALGkEADxpBAAsIAEEADxpBAAsLnAcjAEGAEAsQDAAAAGFmZmluZXNjcmlwdABBkBALHRkAAABObyBBZmZpbmVTY3JpcHQgZmlsZSBvcGVuAEGtEAsEAAAAAABBsRALEQ0AAABhZmZpbmVzY3JpcHQgAEHCEAsGAgAAACAiAEHIEAsFAQAAACIAQc0QCwkFAAAAY2hlY2sAQdYQCwsHAAAALmFmZmluZQBB4RALFhIAAABBZmZpbmVTY3JpcHQgQ2hlY2sAQfcQCwgEAAAAZXZhbABB/xALFREAAABBZmZpbmVTY3JpcHQgRXZhbABBlBELCwcAAABjb21waWxlAEGfEQsJBQAAAC53YXNtAEGoEQsaFgAAAGFmZmluZXNjcmlwdCBjb21waWxlICIAQcIRCwoGAAAAIiAtbyAiAEHMEQsYFAAAAEFmZmluZVNjcmlwdCBDb21waWxlAEHkEQsKBgAAAGZvcm1hdABB7hELBwMAAABmbXQAQfURCx8bAAAARmlsZSBmb3JtYXR0ZWQgc3VjY2Vzc2Z1bGx5AEGUEgsVEQAAAEZvcm1hdHRpbmcgZmFpbGVkAEGpEgs3MwAAAEFmZmluZVNjcmlwdCBMU1Agc3RvcHBlZCAocmVsb2FkIHdpbmRvdyB0byByZXN0YXJ0KQBB4BILEg4AAABsc3Auc2VydmVyUGF0aABB8hILFBAAAABhZmZpbmVzY3JpcHQtbHNwAEGGEwsKBgAAAHdoaWNoIABBkBMLQj4AAABBZmZpbmVTY3JpcHQgTFNQIHNlcnZlciBub3QgZm91bmQuIExhbmd1YWdlIGZlYXR1cmVzIGRpc2FibGVkLgBB0hMLEw8AAABhZmZpbmVzY3JpcHRMc3AAQeUTCyAcAAAAQWZmaW5lU2NyaXB0IExhbmd1YWdlIFNlcnZlcgBBhRQLHBgAAABBZmZpbmVTY3JpcHQgTFNQIHN0YXJ0ZWQAQaEUCyQgAAAAQWZmaW5lU2NyaXB0IGV4dGVuc2lvbiBhY3RpdmF0ZWQAQcUUCxYSAAAAYWZmaW5lc2NyaXB0LmNoZWNrAEHbFAsVEQAAAGFmZmluZXNjcmlwdC5ldmFsAEHwFAsYFAAAAGFmZmluZXNjcmlwdC5jb21waWxlAEGIFQsXEwAAAGFmZmluZXNjcmlwdC5mb3JtYXQAQZ8VCxsXAAAAYWZmaW5lc2NyaXB0LnJlc3RhcnRMc3AAQboVCw8LAAAAbHNwLmVuYWJsZWQAYxZhZmZpbmVzY3JpcHQub3duZXJzaGlwCwAAABYAAAACAAAAFwAAAAEAABgAAAACAAAAGQAAAAAAGgAAAAAAGwAAAAAAHAAAAAAAHQAAAAAAHgAAAAAAHwAAAAEAACAAAAAAAA==";
12+
const _wasmBytes = Buffer.from(_wasmBase64, "base64");
13+
14+
// Per-process opaque-handle table for host objects (ExtensionContext,
15+
// Terminal, LanguageClient, ...). Wasm refers to host objects by integer id.
16+
const _handles = new Map();
17+
let _nextHandle = 1;
18+
function _registerHandle(obj) {
19+
const h = _nextHandle++;
20+
_handles.set(h, obj);
21+
return h;
22+
}
23+
function _getHandle(h) { return _handles.get(h); }
24+
function _freeHandle(h) { _handles.delete(h); }
25+
26+
// WASI-style minimal imports — affinescript codegen wires in fd_write on
27+
// every module so we satisfy that even if no IO is exercised.
28+
function _writeFdString(fd, ptr, len, memory) {
29+
const bytes = new Uint8Array(memory.buffer, ptr, len);
30+
const text = new TextDecoder("utf-8").decode(bytes);
31+
if (fd === 1) process.stdout.write(text);
32+
else if (fd === 2) process.stderr.write(text);
33+
}
34+
35+
let _instance = null;
36+
let _memory = null;
37+
38+
function _buildImports() {
39+
const wasi_snapshot_preview1 = {
40+
fd_write: (fd, iovs_ptr, iovs_len, nwritten_ptr) => {
41+
// Minimal fd_write: walk the iovec array and concat to fd.
42+
const view = new DataView(_memory.buffer);
43+
let total = 0;
44+
for (let i = 0; i < iovs_len; i++) {
45+
const ptr = view.getUint32(iovs_ptr + i * 8, true);
46+
const len = view.getUint32(iovs_ptr + i * 8 + 4, true);
47+
_writeFdString(fd, ptr, len, _memory);
48+
total += len;
49+
}
50+
view.setUint32(nwritten_ptr, total, true);
51+
return 0;
52+
},
53+
};
54+
// Phase 2 hook: callers can replace exports.extraImports with a function
55+
// returning a `{ ModuleName: { exportName: fn, ... } }` map of concrete
56+
// host bindings (e.g. the @hyperpolymath/affine-vscode adapter). Default
57+
// is empty so the shim works standalone.
58+
const extras = (typeof exports.extraImports === "function")
59+
? exports.extraImports()
60+
: {};
61+
return { wasi_snapshot_preview1, ...extras };
62+
}
63+
64+
async function _init() {
65+
if (_instance) return _instance;
66+
const { instance } = await WebAssembly.instantiate(_wasmBytes, _buildImports());
67+
_instance = instance;
68+
_memory = instance.exports.memory;
69+
// Phase 2 hook: the active instance + memory must be reachable from
70+
// bindings adapters that need to read string args / call back into the
71+
// wasm table. Surface them on the exports object as soon as init finishes.
72+
exports._instance = instance;
73+
exports._memory = instance.exports.memory;
74+
return _instance;
75+
}
76+
77+
exports.activate = async function activate(context) {
78+
const inst = await _init();
79+
if (typeof inst.exports.activate === "function") {
80+
const ctxHandle = _registerHandle(context);
81+
return inst.exports.activate(ctxHandle);
82+
}
83+
};
84+
85+
exports.deactivate = async function deactivate() {
86+
if (!_instance) return;
87+
if (typeof _instance.exports.deactivate === "function") {
88+
return _instance.exports.deactivate();
89+
}
90+
};
91+
92+
// Surfaced for Phase 2 binding modules to register concrete vscode-API
93+
// implementations before the Wasm is instantiated.
94+
exports._registerHandle = _registerHandle;
95+
exports._getHandle = _getHandle;
96+
exports._freeHandle = _freeHandle;

editors/vscode/package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"activationEvents": [
2929
"onLanguage:affinescript"
3030
],
31-
"main": "./out/extension.js",
31+
"main": "./out/extension.cjs",
3232
"contributes": {
3333
"languages": [
3434
{
@@ -135,15 +135,14 @@
135135
},
136136
"scripts": {
137137
"vscode:prepublish": "npm run compile",
138-
"compile": "tsc -p ./",
139-
"watch": "tsc -watch -p ./",
138+
"compile": "affinescript compile src/extension.affine -o out/extension.cjs",
139+
"watch": "echo 'watch mode not implemented for AffineScript source — re-run npm run compile'",
140+
"guard": "../../tools/check-no-extension-ts.sh",
140141
"package": "vsce package",
141142
"publish": "vsce publish"
142143
},
143144
"devDependencies": {
144-
"@types/node": "^20.0.0",
145145
"@types/vscode": "^1.80.0",
146-
"typescript": "^5.0.0",
147146
"vsce": "^2.15.0"
148147
},
149148
"dependencies": {

0 commit comments

Comments
 (0)