Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ jobs:
uses: actions/setup-node@v7.0.0
with:
node-version: "20"
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Install dependencies
run: opam install . --deps-only --with-test --with-doc --yes
- name: Install tree-sitter CLI (for res-to-affine walker tests)
Expand All @@ -79,6 +81,8 @@ jobs:
# and runs the *.harness.mjs under Node (CI has Node 20, not Deno;
# the Phase 1 fixtures are pure logic so Node ESM exercises them).
run: opam exec -- ./tools/run_codegen_deno_tests.sh
- name: Run native Bun-ESM tests (issue #734)
run: opam exec -- ./tools/run_codegen_bun_tests.sh
- name: Run face-transformer regression tests
run: opam exec -- ./tools/run_face_transformer_tests.sh
- name: Issue #35 Phase 3 — block extension.ts regression
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ bisect*.coverage
# issue #122: generated Deno-ESM regression outputs (compiled from the
# committed *.affine fixtures by tools/run_codegen_deno_tests.sh).
/tests/codegen-deno/*.deno.js
# Issue #734: generated native Bun-ESM acceptance outputs.
/tests/codegen-bun/*.bun.js
/tests/codegen-bun/backend-conflict.json
# Local-only build workaround (see file header); never committed.
/dune-workspace
packages/affinescript-cli/deno.lock
Expand Down
62 changes: 53 additions & 9 deletions bin/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -490,9 +490,21 @@ let repl_cmd_fn () =
compilation errors. With [--wasm-gc], targets the WebAssembly GC
proposal instead of WASM 1.0 linear memory. *)
let compile_file face json wasm_gc vscode_ext vscode_adapter vscode_no_lc
deno_esm target path output =
deno_esm bun_esm target path output =
let face = resolve_face ~quiet:json face path in
if json then begin
let is_deno = deno_esm || Filename.check_suffix output ".deno.js" in
let is_bun = bun_esm || Filename.check_suffix output ".bun.js" in
if is_deno && is_bun then
let message = "--deno-esm and --bun-esm are mutually exclusive" in
if json then
json_finish [{ Affinescript.Json_output.severity = Error;
code = "E0826"; message;
span = Affinescript.Span.dummy; help = None; labels = [] }]
else begin
Format.eprintf "@[<v>Backend selection error: %s@]@." message;
`Error (false, "Backend selection error")
end
else if json then begin
let diags = ref [] in
let add d = diags := d :: !diags in
begin try
Expand Down Expand Up @@ -525,8 +537,9 @@ let compile_file face json wasm_gc vscode_ext vscode_adapter vscode_no_lc
via Codegen.gen_imports / the import section. *)
let flat_prog = Affinescript.Module_loader.flatten_imports loader prog in
let is_deno = deno_esm || Filename.check_suffix output ".deno.js" in
let is_bun = bun_esm || Filename.check_suffix output ".bun.js" in
let is_julia = Filename.check_suffix output ".jl" in
let is_js = (not is_deno) && Filename.check_suffix output ".js" in
let is_js = (not is_deno) && (not is_bun) && Filename.check_suffix output ".js" in
let is_c = Filename.check_suffix output ".c" in
let is_wgsl = Filename.check_suffix output ".wgsl" in
let is_faust = Filename.check_suffix output ".dsp" in
Expand All @@ -547,14 +560,24 @@ let compile_file face json wasm_gc vscode_ext vscode_adapter vscode_no_lc
let is_why3 = Filename.check_suffix output ".mlw" in
let is_lean = Filename.check_suffix output ".lean" in
let is_spirv = Filename.check_suffix output ".spv" in
if is_deno then begin
if is_bun then begin
match Affinescript.Codegen_deno.codegen_bun flat_prog resolve_ctx.symbols with
| Error msg ->
add { severity = Error; code = "E0825";
message = msg;
span = Affinescript.Span.dummy; help = None; labels = [] }
| Ok esm_code ->
let oc = open_out_bin output in
output_string oc esm_code;
close_out oc
end else if is_deno then begin
match Affinescript.Codegen_deno.codegen_deno flat_prog resolve_ctx.symbols with
| Error msg ->
add { severity = Error; code = "E0824";
message = Printf.sprintf "Deno-ESM codegen error: %s" msg;
span = Affinescript.Span.dummy; help = None; labels = [] }
| Ok esm_code ->
let oc = open_out output in
let oc = open_out_bin output in
output_string oc esm_code;
close_out oc
end else if is_julia then begin
Expand Down Expand Up @@ -757,8 +780,9 @@ let compile_file face json wasm_gc vscode_ext vscode_adapter vscode_no_lc
module-system support. Wasm/Wasm-GC keep the original [prog]. *)
let flat_prog = Affinescript.Module_loader.flatten_imports loader prog in
let is_deno = deno_esm || Filename.check_suffix output ".deno.js" in
let is_bun = bun_esm || Filename.check_suffix output ".bun.js" in
let is_julia = Filename.check_suffix output ".jl" in
let is_js = (not is_deno) && Filename.check_suffix output ".js" in
let is_js = (not is_deno) && (not is_bun) && Filename.check_suffix output ".js" in
let is_c = Filename.check_suffix output ".c" in
let is_wgsl = Filename.check_suffix output ".wgsl" in
let is_faust = Filename.check_suffix output ".dsp" in
Expand All @@ -779,7 +803,18 @@ let compile_file face json wasm_gc vscode_ext vscode_adapter vscode_no_lc
let is_why3 = Filename.check_suffix output ".mlw" in
let is_lean = Filename.check_suffix output ".lean" in
let is_spirv = Filename.check_suffix output ".spv" in
if is_deno then
if is_bun then
(match Affinescript.Codegen_deno.codegen_bun flat_prog resolve_ctx.symbols with
| Error e ->
Format.eprintf "@[<v>%s@]@." e;
`Error (false, "Bun-ESM codegen error")
| Ok esm_code ->
let oc = open_out_bin output in
output_string oc esm_code;
close_out oc;
Format.printf "Compiled %s -> %s (Bun-ESM)@." path output;
`Ok ())
else if is_deno then
(match Affinescript.Codegen_deno.codegen_deno flat_prog resolve_ctx.symbols with
| Error e ->
Format.eprintf "@[<v>Deno-ESM codegen error: %s@]@." e;
Expand Down Expand Up @@ -1259,6 +1294,15 @@ let deno_esm_arg =
no handle table — the output is a drop-in importable ESM. A \
`.deno.js` output extension selects this backend implicitly.")

let bun_esm_arg =
Arg.(value & flag & info ["bun-esm"]
~doc:"Emit a standalone Bun-native ES module directly from the AST: \
public declarations are exported and host operations use Bun's \
Node-compatible synchronous APIs. The emitted module contains no \
legacy-runtime shim. A `.bun.js` output extension selects this \
backend implicitly. This option is mutually exclusive with \
`--deno-esm`.")

(** Shared --face flag: select the parser surface-syntax face. *)
let face_arg =
let faces = Arg.enum [
Expand Down Expand Up @@ -1602,11 +1646,11 @@ let repl_cmd =
Cmd.v info Term.(ret (const repl_cmd_fn $ const ()))

let compile_cmd =
let doc = "Compile a file to WebAssembly (1.0 or GC proposal), Julia (.jl), JavaScript (.js), C (.c), a WGSL compute kernel (.wgsl), a Faust DSP program (.dsp), or an ONNX model (.onnx)" in
let doc = "Compile a file to WebAssembly (1.0 or GC proposal), native Bun ESM (.bun.js/--bun-esm), Julia (.jl), JavaScript (.js), C (.c), a WGSL compute kernel (.wgsl), a Faust DSP program (.dsp), or an ONNX model (.onnx)" in
let info = Cmd.info "compile" ~doc in
Cmd.v info Term.(ret (const compile_file $ face_arg $ json_arg $ wasm_gc_arg
$ vscode_ext_arg $ vscode_adapter_arg $ vscode_no_lc_arg $ deno_esm_arg
$ target_arg $ path_arg $ output_arg))
$ bun_esm_arg $ target_arg $ path_arg $ output_arg))

let fmt_cmd =
let doc = "Format a file" in
Expand Down
11 changes: 11 additions & 0 deletions docs/CAPABILITY-MATRIX.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@ Producer-side; target spec is the separate `hyperpolymath/typed-wasm` repo.
|Deno-ESM |works |Direct AST→ES-module transpiler (`lib/codegen_deno.ml`),
`--deno-esm` / `.deno.js`. Shipped + consumer-verified (ubicity).

|Bun-ESM |works |Host-profiled direct AST→ES-module transpiler,
`--bun-esm` / `.bun.js`. Public declarations remain importable ESM; filesystem,
argument, and process operations lower through Bun's synchronous
Node-compatibility surface. Host operations are resolved lazily so a generated
module that only uses browser-provided externs remains browser-importable. CI
compiles, checks for legacy-runtime leakage, parses, and executes filesystem,
byte, argument, export, and missing-path controls under Bun.
Comment thread
hyperpolymath marked this conversation as resolved.
Migration and host-contract details:
link:guides/bun-esm-migration.adoc[guides/bun-esm-migration.adoc].

|Node-CJS |works |`lib/codegen_node.ml`; CJS shim, handle table, the
`.affine` VS Code extension compiles through it. Live-host smoke
harness for the compiled extension landed via PR #317 / issue #139:
Expand Down Expand Up @@ -250,6 +260,7 @@ test is deleted or renamed without this section (and CI) noticing.
|typed-wasm isolation |`test/test_tw_isolation.ml`
|Codegen (golden snapshots) |`test/test_golden.ml`
|Deno / JS host backends |`test/test_deno_builtins_consistency.ml`, `test/test_int_div_js.ml`
|Bun-ESM host backend |`tools/run_codegen_bun_tests.sh`
|Solo core (executable metatheory) |`test/test_solo_cesk.ml`
|===

Expand Down
71 changes: 71 additions & 0 deletions docs/guides/bun-esm-migration.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
= Migrating direct ESM output to Bun
:toc: macro

toc::[]

== Select the Bun host profile

Replace `--deno-esm` with `--bun-esm`, and replace generated `.deno.js` names
with `.bun.js`. A `.bun.js` output suffix selects the profile implicitly, but
the explicit flag is preferable in build definitions because it records the
runtime contract directly.

[source,console]
----
affinescript compile --bun-esm -o build/library.bun.js src/library.affine
bun --check build/library.bun.js
----

The compiler emits public functions, constants, variants, and generated
classes as standard ESM exports. Bun output is checked in CI to contain no
case-insensitive reference to the former runtime.

== Host operations

The shared ESM core contains runtime-neutral lowering for JSON, URL,
WebAssembly, web APIs, and pure language operations. The Bun profile supplies
its own filesystem, arguments, process, environment, subprocess, standard-I/O,
and exit lowerings. Synchronous filesystem and subprocess operations use Bun's
Node-compatibility surface. They are resolved lazily, so a generated module
that only calls browser-provided externs can still load in a webview with no
`process` global.

Import explicit Bun process capabilities from `stdlib/Bun.affine`:

[source,affinescript]
----
use Bun::{ bun_env_get, bun_run, bun_stdin_text,
bun_stdout_write, bun_stderr_write, bun_exit };
----

Names beginning with `bun_` form a checked capability namespace. If such an
extern has no compiler lowering, Bun code generation fails non-zero. This is a
deliberate negative guarantee: a misspelt or unimplemented host operation must
not silently turn into an optimistic JavaScript call. Other `extern fn` names
retain the existing direct-ESM contract and lower to same-named host symbols;
that facility is required for explicit webview bridges such as Gossamer.

== Runtime-neutral core and compatibility

`lib/codegen_deno.ml` currently owns the mature direct-ESM lowering machinery,
but its generated runtime is split into three parts: a compatibility host
profile, a Bun host profile, and a shared runtime-neutral prelude/builtin core.
The source filename is retained to avoid a high-risk mechanical move in the
same change as the semantic migration. It does not imply that Bun output passes
through or embeds the compatibility runtime.

The compatibility flag remains available for existing external consumers. An
estate migration is complete only when its active workflows, runtime files,
lockfiles, build commands, generated artefact names, examples, and current
documentation have moved to Bun. Historical records may retain their original
runtime names when clearly marked as history; renaming history would make it
less accurate.

== Verification contract

`tools/run_codegen_bun_tests.sh` provides positive and planted-negative
controls. It compiles and imports public exports, exercises filesystem and byte
I/O, arguments, environment values, subprocess status, stdin, and explicit
exit status under Bun; checks JavaScript syntax and absence of legacy-runtime
references; compiles twice and compares output byte-for-byte; and proves that
an unsupported `bun_` capability fails compilation.
Loading
Loading