From 6a2abb59b87f14feb2b327de1fba0175dc5887ba Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 23:39:01 +0100 Subject: [PATCH] =?UTF-8?q?feat(cli):=20Phase=20D=20=E2=80=94=20compile-ep?= =?UTF-8?q?h=20/=20compile-affine=20aliases=20(closes=20#36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clap visible aliases so the CLI can be invoked as `compile-eph` and `compile-affine`, both routing to the `compile` subcommand. Probed by hypatia's build-gossamer-gui.yml workflow. Salvaged from the diverged ephapax v2-grammar branch onto current main. The superseded v2 extern/grammar phases (A/B/C/E/F-H/I/J) are NOT included — upstream main already ships a more complete extern implementation with a different API. The single test fixture `tests/v2-grammar/fixtures/ extern-callsite.eph` is carried from the dropped Phase B commit (6f7f316) so the Phase D acceptance test is self-contained; verified to compile to valid wasm on current main via the new aliases. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/ephapax-cli/Cargo.toml | 1 + src/ephapax-cli/src/main.rs | 9 ++- .../tests/v2_grammar_phase_d_aliases.rs | 61 +++++++++++++++++++ tests/v2-grammar/fixtures/extern-callsite.eph | 17 ++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/ephapax-cli/tests/v2_grammar_phase_d_aliases.rs create mode 100644 tests/v2-grammar/fixtures/extern-callsite.eph diff --git a/src/ephapax-cli/Cargo.toml b/src/ephapax-cli/Cargo.toml index 0ea87ac1..141f1dcf 100644 --- a/src/ephapax-cli/Cargo.toml +++ b/src/ephapax-cli/Cargo.toml @@ -36,4 +36,5 @@ serde_json.workspace = true [dev-dependencies] wasmtime.workspace = true proptest = { workspace = true } +wasmparser = "0.221" tempfile = "3" diff --git a/src/ephapax-cli/src/main.rs b/src/ephapax-cli/src/main.rs index fa4c1e66..b4a12eba 100644 --- a/src/ephapax-cli/src/main.rs +++ b/src/ephapax-cli/src/main.rs @@ -73,7 +73,14 @@ enum Commands { mode: String, }, - /// Compile to WebAssembly + /// Compile to WebAssembly. + /// + /// Aliased as `compile-eph` and `compile-affine` for downstream + /// consumers (notably hypatia's `build-gossamer-gui` workflow, which + /// probes for these names in order). All three names route to the same + /// surface-parse → desugar → typecheck → wasm pipeline. + /// Closes hyperpolymath/ephapax#36. + #[command(alias = "compile-eph", alias = "compile-affine")] Compile { /// Input file file: PathBuf, diff --git a/src/ephapax-cli/tests/v2_grammar_phase_d_aliases.rs b/src/ephapax-cli/tests/v2_grammar_phase_d_aliases.rs new file mode 100644 index 00000000..46513a26 --- /dev/null +++ b/src/ephapax-cli/tests/v2_grammar_phase_d_aliases.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later +// +// Phase D: clap aliases for the `compile` subcommand. Closes #36. +// Asserts that invoking the CLI as `compile-eph` and `compile-affine` +// both route to the same `Compile` subcommand. Probed by hypatia's +// `build-gossamer-gui.yml` workflow (see #36 for the probe order). + +use std::process::Command; + +fn ephapax_bin() -> String { + // CARGO_BIN_EXE_ephapax is set by cargo for integration tests. + env!("CARGO_BIN_EXE_ephapax").to_string() +} + +fn fixture(name: &str) -> String { + format!( + "{}/../../tests/v2-grammar/fixtures/{}", + env!("CARGO_MANIFEST_DIR"), + name + ) +} + +#[test] +fn compile_eph_alias_routes_to_compile() { + let out = tempfile::NamedTempFile::new().expect("temp file"); + let status = Command::new(ephapax_bin()) + .arg("compile-eph") + .arg(fixture("extern-callsite.eph")) + .arg("-o") + .arg(out.path()) + .output() + .expect("ephapax compile-eph must run"); + assert!( + status.status.success(), + "compile-eph alias failed: stdout={} stderr={}", + String::from_utf8_lossy(&status.stdout), + String::from_utf8_lossy(&status.stderr) + ); + let bytes = std::fs::read(out.path()).expect("output wasm exists"); + assert!(bytes.starts_with(b"\0asm"), "wasm magic bytes present"); +} + +#[test] +fn compile_affine_alias_routes_to_compile() { + let out = tempfile::NamedTempFile::new().expect("temp file"); + let status = Command::new(ephapax_bin()) + .arg("compile-affine") + .arg(fixture("extern-callsite.eph")) + .arg("-o") + .arg(out.path()) + .output() + .expect("ephapax compile-affine must run"); + assert!( + status.status.success(), + "compile-affine alias failed: stdout={} stderr={}", + String::from_utf8_lossy(&status.stdout), + String::from_utf8_lossy(&status.stderr) + ); + let bytes = std::fs::read(out.path()).expect("output wasm exists"); + assert!(bytes.starts_with(b"\0asm"), "wasm magic bytes present"); +} diff --git a/tests/v2-grammar/fixtures/extern-callsite.eph b/tests/v2-grammar/fixtures/extern-callsite.eph new file mode 100644 index 00000000..b1bf26fd --- /dev/null +++ b/tests/v2-grammar/fixtures/extern-callsite.eph @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later +// Extern fn callsite — proves Phase B of #43: the extern signature is +// in scope from the function body. `entry()` returns the result of an +// `extern "wasm"`-declared identity function on a literal. +// +// The `type` extern item is currently dropped by desugar (Phase B+ will +// register it as an abstract type). The `fn` extern item desugars to +// `Decl::Extern`, gets registered in the typechecker env by the +// pre-pass, and the call resolves cleanly. + +module hyperpolymath/ephapax/test + +extern "wasm" { + fn host_identity(x: I32): I32 +} + +fn entry(): I32 = host_identity(7)