Skip to content

Commit 53c96dc

Browse files
refactor: semantic port TS to AffineScript (#724)
Automated semantic porting.
1 parent 4ead72f commit 53c96dc

8 files changed

Lines changed: 120 additions & 153 deletions

File tree

affinescript-deno-test/cli.affine

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// Ported via Harvard Engine mechanical processor
2+
// Ported via Harvard Engine (Semantic pass)
33

44
module cli;
55

6-
// TODO: Complete semantic implementation
7-
8-
/* === ORIGINAL TYPESCRIPT CONTEXT ===
96
// SPDX-License-Identifier: MPL-2.0
107
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
118
//
@@ -27,7 +24,7 @@ module cli;
2724
import { compileToWasm } from "./lib/compile.ts";
2825
import { discoverTestFiles } from "./lib/discover.ts";
2926

30-
function usage(): never {
27+
fn usage(): never {
3128
console.error(
3229
"Usage: deno run --allow-read --allow-run --allow-env cli.ts <root>\n" +
3330
"\n" +
@@ -39,10 +36,10 @@ function usage(): never {
3936
}
4037

4138
if (import.meta.main) {
42-
const root = Deno.args[0];
39+
let root = Deno.args[0];
4340
if (!root) usage();
4441

45-
const sources = await discoverTestFiles(root);
42+
let sources = await discoverTestFiles(root);
4643
if (sources.length === 0) {
4744
console.error(`No *_test.affine files found under ${root}`);
4845
Deno.exit(1);
@@ -51,14 +48,13 @@ if (import.meta.main) {
5148
console.log(`Discovered ${sources.length} test file(s):`);
5249
for (const source of sources) {
5350
try {
54-
const wasm = await compileToWasm(source);
51+
let wasm = await compileToWasm(source);
5552
console.log(` ✓ ${source} → ${wasm}`);
5653
} catch (error) {
57-
const message = error instanceof Error ? error.message : String(error);
54+
let message = error instanceof Error ? error.message : String(error);
5855
console.error(` ✗ ${source}\n ${message}`);
5956
Deno.exit(1);
6057
}
6158
}
6259
}
6360

64-
==================================== */

affinescript-deno-test/example/smoke_driver.affine

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// Ported via Harvard Engine mechanical processor
2+
// Ported via Harvard Engine (Semantic pass)
33

44
module smoke_driver;
55

6-
// TODO: Complete semantic implementation
7-
8-
/* === ORIGINAL TYPESCRIPT CONTEXT ===
96
// SPDX-License-Identifier: MPL-2.0
107
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
118
//
@@ -21,4 +18,3 @@ import { runAll } from "../mod.ts";
2118

2219
await runAll(new URL("./", import.meta.url).pathname);
2320

24-
==================================== */
Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// Ported via Harvard Engine mechanical processor
2+
// Ported via Harvard Engine (Semantic pass)
33

44
module compile;
55

6-
// TODO: Complete semantic implementation
7-
8-
/* === ORIGINAL TYPESCRIPT CONTEXT ===
96
// SPDX-License-Identifier: MPL-2.0
107
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
118
//
@@ -14,16 +11,16 @@ module compile;
1411
// Wraps the `affinescript compile` CLI. Given a `.affine` source file,
1512
// produces a sibling `.wasm` file and returns its absolute path.
1613
//
17-
// The `AFFINESCRIPT_BIN` env var overrides the default path to the compiler.
14+
// The `AFFINESCRIPT_BIN` env let overrides the default path to the compiler.
1815
// Default is the local dev-build at developer-ecosystem/nextgen-languages/
1916
// affinescript/_build/install/default/bin/affinescript (useful while the
2017
// compiler is not on $PATH).
2118

22-
const DEFAULT_BIN =
19+
let DEFAULT_BIN =
2320
"/var/mnt/eclipse/repos/developer-ecosystem/nextgen-languages/affinescript/_build/install/default/bin/affinescript";
2421

2522
/** Absolute path to the `affinescript` compiler binary. */
26-
export function resolveCompilerPath(): string {
23+
fn resolveCompilerPath(): string {
2724
return Deno.env.get("AFFINESCRIPT_BIN") ?? DEFAULT_BIN;
2825
}
2926

@@ -32,29 +29,29 @@ export function resolveCompilerPath(): string {
3229
* path to the emitted `.wasm`. Throws with compiler stderr if compilation
3330
* fails.
3431
*/
35-
export async function compileToWasm(sourcePath: string): Promise<string> {
36-
const absolute = sourcePath.startsWith("/")
32+
async fn compileToWasm(sourcePath: string): string {
33+
let absolute = sourcePath.startsWith("/")
3734
? sourcePath
3835
: `${Deno.cwd()}/${sourcePath}`;
3936

40-
const wasmPath = absolute.replace(/\.(affine|afs|rattle|pyaff|jsaff)$/, ".wasm");
37+
let wasmPath = absolute.replace(/\.(affine|afs|rattle|pyaff|jsaff)$/, ".wasm");
4138
if (wasmPath === absolute) {
4239
throw new Error(
4340
`compileToWasm: source file must end in .affine / .afs / .rattle / .pyaff / .jsaff — got ${sourcePath}`,
4441
);
4542
}
4643

47-
const bin = resolveCompilerPath();
48-
const cmd = new Deno.Command(bin, {
44+
let bin = resolveCompilerPath();
45+
let cmd = new Deno.Command(bin, {
4946
args: ["compile", absolute, "-o", wasmPath],
5047
stdout: "piped",
5148
stderr: "piped",
5249
});
5350

5451
const { code, stdout, stderr } = await cmd.output();
5552
if (code !== 0) {
56-
const out = new TextDecoder().decode(stdout);
57-
const err = new TextDecoder().decode(stderr);
53+
let out = new TextDecoder().decode(stdout);
54+
let err = new TextDecoder().decode(stderr);
5855
throw new Error(
5956
`affinescript compile failed (exit ${code}) for ${sourcePath}\n` +
6057
`STDOUT:\n${out}\nSTDERR:\n${err}`,
@@ -64,4 +61,3 @@ export async function compileToWasm(sourcePath: string): Promise<string> {
6461
return wasmPath;
6562
}
6663

67-
==================================== */
Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// Ported via Harvard Engine mechanical processor
2+
// Ported via Harvard Engine (Semantic pass)
33

44
module discover;
55

6-
// TODO: Complete semantic implementation
7-
8-
/* === ORIGINAL TYPESCRIPT CONTEXT ===
96
// SPDX-License-Identifier: MPL-2.0
107
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
118
//
129
// affinescript-deno-test: discover.ts
1310
//
1411
// Glob-based discovery of AffineScript test files.
15-
// Default convention: any file matching `*_test.affine` or `*.test.affine`.
12+
// Default convention: unknown file matching `*_test.affine` or `*.test.affine`.
1613

1714
import { walk } from "jsr:@std/fs@1/walk";
1815

1916
/** Default regex for matching AffineScript test files by filename. */
20-
export const DEFAULT_TEST_PATTERN = /(?:_test|\.test)\.(?:affine|afs|rattle|pyaff|jsaff)$/;
17+
let DEFAULT_TEST_PATTERN = /(?:_test|\.test)\.(?:affine|afs|rattle|pyaff|jsaff)$/;
2118

2219
/**
2320
* Recursively walk `root` and return absolute paths of files matching
2421
* `pattern` (default: `*_test.affine` or `*.test.affine`).
2522
*/
26-
export async function discoverTestFiles(
23+
async fn discoverTestFiles(
2724
root: string,
2825
pattern: RegExp = DEFAULT_TEST_PATTERN,
29-
): Promise<string[]> {
30-
const absoluteRoot = root.startsWith("/") ? root : `${Deno.cwd()}/${root}`;
26+
): string[] {
27+
let absoluteRoot = root.startsWith("/") ? root : `${Deno.cwd()}/${root}`;
3128
const matches: string[] = [];
3229

3330
for await (const entry of walk(absoluteRoot, { includeDirs: false, match: [pattern] })) {
@@ -38,4 +35,3 @@ export async function discoverTestFiles(
3835
return matches;
3936
}
4037

41-
==================================== */

affinescript-deno-test/lib/runner.affine

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// Ported via Harvard Engine mechanical processor
2+
// Ported via Harvard Engine (Semantic pass)
33

44
module runner;
55

6-
// TODO: Complete semantic implementation
7-
8-
/* === ORIGINAL TYPESCRIPT CONTEXT ===
96
// SPDX-License-Identifier: MPL-2.0
107
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
118
//
129
// affinescript-deno-test: runner.ts
1310
//
1411
// Loads a compiled AffineScript WASM module and wraps every exported function
1512
// whose name starts with `test_` as a Deno.test() case. A test passes when
16-
// the function returns `true`, fails when it returns `false`.
13+
// the fn returns `true`, fails when it returns `false`.
1714
//
1815
// Convention (v0.2.0): each `.affine` file may define multiple tests via
19-
// the `pub fn test_<name>() -> Bool` syntax. Every `pub fn test_*` export
20-
// becomes a separate Deno.test() case. Non-`pub` helpers stay internal to
16+
// the `pub fn test_<name>() -> Bool` syntax. Every `pub fn test_*` // becomes a separate Deno.test() case. Non-`pub` helpers stay internal to
2117
// the module. This relies on the AffineScript compiler honouring `fd_vis`
22-
// in its WASM-export decision (commit ce324fa, both codegen.ml and
18+
// in its WASM-decision (commit ce324fa, both codegen.ml and
2319
// codegen_gc.ml).
2420
//
2521
// Uses the existing @hyperpolymath/affine-js bridge for WASM loading and
@@ -29,23 +25,23 @@ module runner;
2925
import { AffineModule } from "@hyperpolymath/affine-js";
3026

3127
/** Convention: every `pub fn` whose name begins with this prefix is a test. */
32-
export const TEST_PREFIX = "test_";
28+
let TEST_PREFIX = "test_";
3329

3430
/** Result shape returned by AffineScript Bool exports (via affine-js). */
35-
interface BoolValue {
31+
struct BoolValue {
3632
kind: "bool";
3733
value: boolean;
3834
}
3935

4036
/**
41-
* Derive the Deno.test() case name from the file basename + export name.
42-
* For a wasm at `/path/to/math_test.wasm` with export `test_add`, yields
37+
* Derive the Deno.test() case name from the file basename + name.
38+
* For a wasm at `/path/to/math_test.wasm` with `test_add`, yields
4339
* `math / add`.
4440
*/
45-
function caseName(wasmPath: string, exportName: string): string {
46-
const base = wasmPath.split("/").pop() ?? wasmPath;
47-
const fileStem = base.replace(/\.wasm$/, "").replace(/(_test|\.test)$/, "");
48-
const caseStem = exportName.replace(/^test_/, "");
41+
fn caseName(wasmPath: string, exportName: string): string {
42+
let base = wasmPath.split("/").pop() ?? wasmPath;
43+
let fileStem = base.replace(/\.wasm$/, "").replace(/(_test|\.test)$/, "");
44+
let caseStem = exportName.replace(/^test_/, "");
4945
return `${fileStem} / ${caseStem}`;
5046
}
5147

@@ -55,7 +51,7 @@ function caseName(wasmPath: string, exportName: string): string {
5551
* do not use IO. For test modules that only return Bool, we can satisfy this
5652
* with a no-op that reports `0` bytes written on every call.
5753
*/
58-
function makeWasiStub(): WebAssembly.ModuleImports {
54+
fn makeWasiStub(): WebAssembly.ModuleImports {
5955
return {
6056
fd_write: (
6157
_fd: number,
@@ -71,22 +67,22 @@ function makeWasiStub(): WebAssembly.ModuleImports {
7167
}
7268

7369
/**
74-
* Register a Deno.test() case for every `test_*` export in the WASM module
70+
* Register a Deno.test() case for every `test_*` in the WASM module
7571
* at `wasmPath`. Path should be absolute; relative paths resolve against CWD.
7672
*
7773
* Returns the number of tests registered. Throws if no `test_*` exports
7874
* are found (indicating a misconfigured file — at least one `pub fn test_*`
7975
* is expected).
8076
*/
81-
export async function registerTestsFromWasm(wasmPath: string): Promise<number> {
82-
const absolute = wasmPath.startsWith("/")
77+
async fn registerTestsFromWasm(wasmPath: string): number {
78+
let absolute = wasmPath.startsWith("/")
8379
? wasmPath
8480
: `${Deno.cwd()}/${wasmPath}`;
8581

86-
const bytes = await Deno.readFile(absolute);
87-
const wasmMod = await WebAssembly.compile(bytes);
88-
const neededImports = WebAssembly.Module.imports(wasmMod);
89-
const needsWasi = neededImports.some((i) => i.module === "wasi_snapshot_preview1");
82+
let bytes = await Deno.readFile(absolute);
83+
let wasmMod = await WebAssembly.compile(bytes);
84+
let neededImports = WebAssembly.Module.imports(wasmMod);
85+
let needsWasi = neededImports.some((i) => i.module === "wasi_snapshot_preview1");
9086

9187
// AffineModule.fromBytes only supplies imports under the "env" module key,
9288
// so when WASI is required we must take the alternative path: raw
@@ -95,8 +91,8 @@ export async function registerTestsFromWasm(wasmPath: string): Promise<number> {
9591
return await registerTestsWithWasi(bytes, wasmPath);
9692
}
9793

98-
const mod = await AffineModule.fromBytes(bytes);
99-
const testExports = mod.functionExports.filter((name: string) =>
94+
let mod = await AffineModule.fromBytes(bytes);
95+
let testExports = mod.functionExports.filter((name: string) =>
10096
name.startsWith(TEST_PREFIX)
10197
);
10298

@@ -110,7 +106,7 @@ export async function registerTestsFromWasm(wasmPath: string): Promise<number> {
110106

111107
for (const exportName of testExports) {
112108
Deno.test(caseName(wasmPath, exportName), () => {
113-
const result = mod.call(exportName, { returnType: "bool" }) as BoolValue;
109+
let result = mod.call(exportName, { returnType: "bool" }) as BoolValue;
114110
if (result.kind !== "bool") {
115111
throw new Error(
116112
`test '${exportName}' returned non-bool value: ${JSON.stringify(result)}`,
@@ -129,39 +125,39 @@ export async function registerTestsFromWasm(wasmPath: string): Promise<number> {
129125
* wasi_snapshot_preview1. Bypasses AffineModule because its constructor
130126
* only accepts imports under the "env" module key.
131127
*/
132-
async function registerTestsWithWasi(
128+
async fn registerTestsWithWasi(
133129
bytes: Uint8Array,
134130
wasmPath: string,
135-
): Promise<number> {
131+
): number {
136132
// Copy into a fresh ArrayBuffer-backed Uint8Array so the TS BufferSource
137133
// overload matches (Deno's Uint8Array default-types to ArrayBufferLike,
138134
// which the WebAssembly.instantiate overload rejects).
139-
const buf = new Uint8Array(bytes.byteLength);
135+
let buf = new Uint8Array(bytes.byteLength);
140136
buf.set(bytes);
141137
const { instance } = await WebAssembly.instantiate(buf.buffer, {
142138
env: {},
143139
wasi_snapshot_preview1: makeWasiStub(),
144140
});
145141

146-
const testExports = Object.keys(instance.exports).filter(
142+
let testExports = Object.keys(instance.exports).filter(
147143
(name) =>
148144
typeof instance.exports[name] === "function" &&
149145
name.startsWith(TEST_PREFIX),
150146
);
151147

152148
if (testExports.length === 0) {
153-
const available = Object.keys(instance.exports).join(", ");
149+
let available = Object.keys(instance.exports).join(", ");
154150
throw new Error(
155-
`affinescript-deno-test: no '${TEST_PREFIX}*' function exports found in ${wasmPath}. ` +
151+
`affinescript-deno-test: no '${TEST_PREFIX}*' fn exports found in ${wasmPath}. ` +
156152
`Available: [${available}]. ` +
157153
`Each test must be declared as 'pub fn test_<name>() -> Bool'.`,
158154
);
159155
}
160156

161157
for (const exportName of testExports) {
162-
const fn = instance.exports[exportName] as () => number;
158+
let fn = instance.exports[exportName] as () => number;
163159
Deno.test(caseName(wasmPath, exportName), () => {
164-
const raw = fn();
160+
let raw = fn();
165161
// AffineScript compiles Bool to i32 (0 = false, 1 = true).
166162
if (raw !== 0 && raw !== 1) {
167163
throw new Error(
@@ -177,4 +173,3 @@ async function registerTestsWithWasi(
177173
return testExports.length;
178174
}
179175

180-
==================================== */

0 commit comments

Comments
 (0)