Skip to content

Commit 38c4e4f

Browse files
hyperpolymathclaude
andcommitted
test(faces): prove a Python-face program runs end-to-end (face → wasm → result)
The README's headline is "faces are the product" — a program in a familiar surface, elaborated to the affine core, compiled to wasm. That path had never been exercised as *running code*, only parsed. This proves it: - test/e2e/fixtures/python_face_runnable.pyaff — Python-shaped source (`def`, `if/else`, recursion), `# face: python` pragma. - test/e2e/python_face_e2e.sh — compiles it THROUGH the Python face to core-WASM and asserts main() === 5170 (fac(5)=120 + sum_to(100)=5050). Skips loudly (exit 0) without the compiler or node; AFFINESCRIPT_BIN overrides the binary path. Observed (node 26, dune 3.24 build): Compiled fixtures/python_face_runnable.pyaff -> pf.wasm (WASM) main() = 5170 (expected 5170) PASS: Python-face program ran end-to-end (face -> wasm -> correct result) Recursion, not a loop, on purpose: proving the face path surfaced two real bugs, filed with minimal repros: - #683 — the Python face drops the trailing `;` on the last statement of a while/for body (tail-position detection doesn't distinguish loop bodies from value bodies), so face-authored loops don't yet compile. Recursion's tail line is a value expression, which the face handles correctly. - #682 — `total` is rejected as a variable name in let-binding / assignment position (the `TOTAL` soft-keyword's ident-recovery is incomplete). The fixture avoids that name. So this is an honest end-to-end proof of the face→wasm path today, and a regression lock, with the known loop gap tracked in #683. Gates: dune build 0; face e2e PASS; doc-truthing OK; soundness ledger all-5 OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4a6a31f commit 38c4e4f

2 files changed

Lines changed: 67 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# face: python
2+
# SPDX-License-Identifier: MPL-2.0
3+
#
4+
# End-to-end proof (2026-07-07) that a *face-authored* program compiles
5+
# through the Python face → canonical → typecheck → wasm and RUNS with the
6+
# right answer. This is the README's headline claim ("faces are the product")
7+
# exercised for real, not just parsed.
8+
#
9+
# Recursion, not a loop: the Python face currently drops the trailing `;` on
10+
# the last statement of a while/for body (issue #683), so face-authored loops
11+
# don't yet compile. Recursion's tail line is a value expression, which the
12+
# face handles correctly — so this proves the face→wasm path without waiting
13+
# on #683. (Variable names also avoid the `total` soft-keyword collision,
14+
# issue #682.)
15+
16+
def fac(n: Int) -> Int:
17+
if n == 0:
18+
1
19+
else:
20+
n * fac(n - 1)
21+
22+
def sum_to(n: Int) -> Int:
23+
if n == 0:
24+
0
25+
else:
26+
n + sum_to(n - 1)
27+
28+
def main() -> Int:
29+
# fac(5)=120, sum_to(100)=5050 -> 120 + 5050 = 5170
30+
fac(5) + sum_to(100)

test/e2e/python_face_e2e.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
#
4+
# End-to-end proof that a Python-face-authored program runs: compile
5+
# fixtures/python_face_runnable.pyaff *through the Python face* to core-WASM
6+
# and assert main() === 5170 (fac(5)=120 + sum_to(100)=5050). Exercises the
7+
# README's "faces are the product" claim as running code, not just a parse.
8+
#
9+
# Requires the compiler built (dune build → _build/default/bin/main.exe;
10+
# override with AFFINESCRIPT_BIN) and node on PATH. Skips loudly (exit 0)
11+
# if either is absent.
12+
set -uo pipefail
13+
cd "$(dirname "$0")"
14+
REPO="$(cd ../.. && pwd)"
15+
BIN="${AFFINESCRIPT_BIN:-$REPO/_build/default/bin/main.exe}"
16+
SRC="fixtures/python_face_runnable.pyaff"
17+
EXPECT=5170
18+
19+
[ -x "$BIN" ] || { echo "SKIP: compiler not built ($BIN) — run dune build"; exit 0; }
20+
command -v node >/dev/null 2>&1 || { echo "SKIP: node not on PATH"; exit 0; }
21+
22+
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
23+
WASM="$TMP/pf.wasm"
24+
25+
# The `# face: python` pragma on line 1 selects the face; no --face needed.
26+
"$BIN" compile "$SRC" -o "$WASM" || { echo "FAIL: compile through Python face"; exit 1; }
27+
28+
node - "$WASM" "$EXPECT" <<'JS'
29+
import { readFile } from "node:fs/promises";
30+
const [wasm, expect] = [process.argv[2], Number(process.argv[3])];
31+
const { instance } = await WebAssembly.instantiate(
32+
await readFile(wasm), { wasi_snapshot_preview1: { fd_write: () => 0 } });
33+
const got = instance.exports.main();
34+
console.log(`main() = ${got} (expected ${expect})`);
35+
if (got !== expect) { console.error("FAIL: wrong result"); process.exit(1); }
36+
console.log("PASS: Python-face program ran end-to-end (face → wasm → correct result)");
37+
JS

0 commit comments

Comments
 (0)