diff --git a/src/ephapax-cli/Cargo.toml b/src/ephapax-cli/Cargo.toml index 0ea87ac..141f1dc 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 fa4c1e6..b4a12eb 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 0000000..46513a2 --- /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 0000000..b1bf26f --- /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)