diff --git a/.gitignore b/.gitignore index 8c0cdbe..cea0896 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,10 @@ dist/ target/ .DS_Store +# Vendored at build time from the linked fink source (see build.mjs step 0a). +src/fink.js + # Claude local config -.claude/settings.local.json -.claude.local/ +.claude /.brain/ diff --git a/Makefile b/Makefile index f775626..7544f69 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ deps-install: node deps.mjs install clean: - rm -rf build crate/pkg + rm -rf build crate/pkg src/fink.js build: NODE_ENV=production node build.mjs diff --git a/build.mjs b/build.mjs index fee3592..c63051a 100644 --- a/build.mjs +++ b/build.mjs @@ -36,6 +36,28 @@ fs.mkdirSync(OUT, { recursive: true }) execSync('wasm-pack build --target web', { cwd: 'crate', stdio: 'inherit' }) console.log(' built crate → crate/pkg/') +// --------------------------------------------------------------------------- +// 0a. Vendor fink.js host shim from the linked fink source. +// Resolves the fink package's manifest path via `cargo metadata` and +// copies src/runtime/interop/js/fink.js next to src/main.ts so esbuild +// can bundle it. Always pulls from the version pinned by Cargo.toml, +// no risk of vendored drift. +// --------------------------------------------------------------------------- + +{ + const meta = JSON.parse(execSync('cargo metadata --format-version 1', { + cwd: 'crate', + stdio: ['ignore', 'pipe', 'inherit'], + }).toString()) + const finkPkg = meta.packages.find((p) => p.name === 'fink') + if (!finkPkg) throw new Error('cargo metadata: fink package not found') + const finkRoot = path.dirname(finkPkg.manifest_path) + const finkJsSrc = path.join(finkRoot, 'src/runtime/interop/js/fink.js') + if (!fs.existsSync(finkJsSrc)) throw new Error(`fink.js not found at ${finkJsSrc}`) + fs.copyFileSync(finkJsSrc, 'src/fink.js') + console.log(` vendored fink.js from ${finkRoot}`) +} + // --------------------------------------------------------------------------- // 1. Monaco editor worker (iife — workers don't use ES modules by default) // --------------------------------------------------------------------------- diff --git a/crate/Cargo.lock b/crate/Cargo.lock index db8c521..9989da5 100644 --- a/crate/Cargo.lock +++ b/crate/Cargo.lock @@ -10,9 +10,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bumpalo" @@ -41,13 +41,13 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fink" version = "0.0.0" -source = "git+https://github.com/fink-lang/fink.git?tag=v0.64.0#365da5fd6902756d1b8f5d61eb2f422cdc6ed035" +source = "git+https://github.com/fink-lang/fink.git?tag=v0.69.0#94ed654681fd72bd2be057b5ce7e33f7d587904c" dependencies = [ - "gimli 0.33.1", - "wasm-encoder 0.248.0", - "wasmparser 0.248.0", + "gimli 0.33.0", + "wasm-encoder", + "wasmparser", "wasmprinter", - "wast 248.0.0", + "wast", "wat", ] @@ -73,9 +73,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ "fallible-iterator", "indexmap", @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.33.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e16c5073773ccf057c282be832a59ee53ef5ff98db3aeff7f8314f52ffc196" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" dependencies = [ "fnv", "hashbrown 0.16.1", @@ -239,9 +239,9 @@ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -252,9 +252,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -275,23 +275,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.246.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" -dependencies = [ - "leb128fmt", - "wasmparser 0.246.2", -] - [[package]] name = "wasm-encoder" version = "0.248.0" @@ -299,18 +289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac92cf547bc18d27ecc521015c08c353b4f18b84ab388bb6d1b6b682c620d9b6" dependencies = [ "leb128fmt", - "wasmparser 0.248.0", -] - -[[package]] -name = "wasmparser" -version = "0.246.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" -dependencies = [ - "bitflags", - "indexmap", - "semver", + "wasmparser", ] [[package]] @@ -334,21 +313,7 @@ checksum = "30b264a5410b008d4d199a92bf536eae703cbd614482fc1ec53831cf19e1c183" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.248.0", -] - -[[package]] -name = "wast" -version = "246.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62" -dependencies = [ - "bumpalo", - "gimli 0.31.1", - "leb128fmt", - "memchr", - "unicode-width", - "wasm-encoder 0.246.2", + "wasmparser", ] [[package]] @@ -358,19 +323,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acc54622ed5a5cddafcdf152043f9d4aed54d4a653d686b7dfe874809fca99d7" dependencies = [ "bumpalo", + "gimli 0.32.3", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.248.0", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.246.2" +version = "1.248.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c" +checksum = "d75cd9e510603909748e6ebab89f27cd04472c1d9d85a3c88a7a6fc51a1a7934" dependencies = [ - "wast 246.0.2", + "wast", ] [[package]] diff --git a/crate/Cargo.toml b/crate/Cargo.toml index 190d6b4..26a7943 100644 --- a/crate/Cargo.toml +++ b/crate/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"] path = "src/lib.rs" [dependencies] -fink = { git = "https://github.com/fink-lang/fink.git", tag = "v0.64.0", default-features = false, features = ["compile"] } +fink = { git = "https://github.com/fink-lang/fink.git", tag = "v0.69.0", default-features = false, features = ["compile"] } wasm-bindgen = "0.2" [profile.release] diff --git a/crate/src/lib.rs b/crate/src/lib.rs index 79d39c8..b1cd7ea 100644 --- a/crate/src/lib.rs +++ b/crate/src/lib.rs @@ -334,6 +334,10 @@ fn collect_tokens(ast: &Ast<'_>, id: AstId, tokens: &mut Vec) { collect_tokens(ast, *operand, tokens); } + NodeKind::PostfixOp { lhs, .. } => { + collect_tokens(ast, *lhs, tokens); + } + NodeKind::Group { inner, .. } => { collect_tokens(ast, *inner, tokens); } @@ -485,6 +489,7 @@ fn node_kind_label<'a>(node: &'a ast::Node<'a>) -> (&'static str, String) { StrRawTempl { .. } => ("StrRawTempl", String::new()), Ident(s) => ("Ident", s.to_string()), UnaryOp { op, .. } => ("UnaryOp", op.src.to_string()), + PostfixOp { op, .. } => ("PostfixOp", op.src.to_string()), InfixOp { op, .. } => ("InfixOp", op.src.to_string()), ChainedCmp(_) => ("ChainedCmp", String::new()), Spread { op, .. } => ("Spread", op.src.to_string()), @@ -529,6 +534,9 @@ fn serialize_children(ast: &Ast<'_>, id: AstId) -> String { UnaryOp { operand, .. } | Try(operand) => { parts.push(serialize_node(ast, *operand)); } + PostfixOp { lhs, .. } => { + parts.push(serialize_node(ast, *lhs)); + } InfixOp { lhs, rhs, .. } | Bind { lhs, rhs, .. } | BindRight { lhs, rhs, .. } @@ -607,7 +615,7 @@ fn native_sourcemap_to_json(sm: &NativeSourceMap) -> String { /// Throws a JS error with the diagnostic message on compilation failure. #[wasm_bindgen] pub fn compile(src: &str) -> Result, JsValue> { - let wasm = fink::to_wasm(src, "playground.fnk") + let wasm = fink::to_wasm_for(src, "playground.fnk", fink::passes::wasm::emit::Interop::Js) .map_err(|e| JsValue::from_str(&e))?; Ok(wasm.binary) } diff --git a/package-lock.json b/package-lock.json index 4c10922..def2706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,9 @@ } }, "node_modules/@actions/core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz", - "integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz", + "integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -39,9 +39,9 @@ } }, "node_modules/@actions/http-client": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz", - "integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz", + "integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==", "dev": true, "license": "MIT", "dependencies": { @@ -50,9 +50,9 @@ } }, "node_modules/@actions/http-client/node_modules/undici": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", - "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", "dev": true, "license": "MIT", "engines": { @@ -1043,13 +1043,13 @@ } }, "node_modules/@types/node": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", - "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/normalize-package-data": { @@ -2064,9 +2064,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2205,9 +2205,9 @@ } }, "node_modules/hosted-git-info": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", - "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", "dev": true, "license": "ISC", "dependencies": { @@ -2455,9 +2455,9 @@ "license": "ISC" }, "node_modules/issue-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", - "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.2.tgz", + "integrity": "sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==", "dev": true, "license": "MIT", "dependencies": { @@ -2523,9 +2523,9 @@ "license": "MIT" }, "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2629,9 +2629,9 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", - "integrity": "sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2864,9 +2864,9 @@ } }, "node_modules/npm": { - "version": "11.12.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.12.1.tgz", - "integrity": "sha512-zcoUuF1kezGSAo0CqtvoLXX3mkRqzuqYdL6Y5tdo8g69NVV3CkjQ6ZBhBgB4d7vGkPcV6TcvLi3GRKPDFX+xTA==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.14.0.tgz", + "integrity": "sha512-6jfZzqK8EfWGnTeZQe+bgMx4IgiEZn7k1eM4GE8SE81B3iYch52dVSMO1hC2OnNbFLIwzvwUkUxsOOcUPG2XIw==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -2945,8 +2945,8 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.4.2", - "@npmcli/config": "^10.8.1", + "@npmcli/arborist": "^9.5.0", + "@npmcli/config": "^10.9.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -2967,24 +2967,24 @@ "hosted-git-info": "^9.0.2", "ini": "^6.0.0", "init-package-json": "^8.2.5", - "is-cidr": "^6.0.3", + "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.5", - "libnpmexec": "^10.2.5", - "libnpmfund": "^7.0.19", + "libnpmdiff": "^8.1.7", + "libnpmexec": "^10.2.7", + "libnpmfund": "^7.0.21", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.5", + "libnpmpack": "^9.1.7", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", "make-fetch-happen": "^15.0.5", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^12.2.0", + "node-gyp": "^12.3.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", @@ -3003,7 +3003,7 @@ "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", - "tar": "^7.5.11", + "tar": "^7.5.13", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", @@ -3092,7 +3092,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.4.2", + "version": "9.5.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3140,7 +3140,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.8.1", + "version": "10.9.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3343,7 +3343,7 @@ } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.5.0", + "version": "0.5.1", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -3485,7 +3485,7 @@ } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "5.0.4", + "version": "5.0.5", "dev": true, "inBundle": true, "license": "MIT", @@ -3554,7 +3554,7 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "5.0.3", + "version": "5.0.5", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -3610,7 +3610,7 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "8.0.3", + "version": "8.0.4", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -3777,7 +3777,7 @@ } }, "node_modules/npm/node_modules/ip-address": { - "version": "10.1.0", + "version": "10.1.1", "dev": true, "inBundle": true, "license": "MIT", @@ -3786,12 +3786,12 @@ } }, "node_modules/npm/node_modules/is-cidr": { - "version": "6.0.3", + "version": "6.0.4", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^5.0.1" + "cidr-regex": "^5.0.4" }, "engines": { "node": ">=20" @@ -3859,12 +3859,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.1.5", + "version": "8.1.7", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.4.2", + "@npmcli/arborist": "^9.5.0", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -3878,13 +3878,13 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.2.5", + "version": "10.2.7", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.4.2", + "@npmcli/arborist": "^9.5.0", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", @@ -3901,12 +3901,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.19", + "version": "7.0.21", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.4.2" + "@npmcli/arborist": "^9.5.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -3926,12 +3926,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.1.5", + "version": "9.1.7", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.4.2", + "@npmcli/arborist": "^9.5.0", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -4001,7 +4001,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.7", + "version": "11.3.5", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -4033,12 +4033,12 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "10.2.4", + "version": "10.2.5", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -4086,35 +4086,17 @@ } }, "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", + "version": "1.0.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.3" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "dev": true, @@ -4194,7 +4176,7 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "12.2.0", + "version": "12.3.0", "dev": true, "inBundle": true, "license": "MIT", @@ -4202,12 +4184,12 @@ "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", + "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { @@ -4580,12 +4562,12 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.7", + "version": "2.8.8", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip-address": "^10.0.1", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -4654,7 +4636,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "7.5.11", + "version": "7.5.13", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -4682,13 +4664,13 @@ "license": "MIT" }, "node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.15", + "version": "0.2.16", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -4715,7 +4697,7 @@ } }, "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", + "version": "4.0.4", "dev": true, "inBundle": true, "license": "MIT", @@ -4749,6 +4731,15 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm/node_modules/undici": { + "version": "6.25.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", "dev": true, @@ -5861,14 +5852,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -5945,9 +5936,9 @@ } }, "node_modules/type-fest": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", - "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { @@ -5961,9 +5952,9 @@ } }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5989,9 +5980,9 @@ } }, "node_modules/undici": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", - "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { @@ -5999,9 +5990,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, diff --git a/src/main.ts b/src/main.ts index cb5f2f8..0ad2322 100644 --- a/src/main.ts +++ b/src/main.ts @@ -516,6 +516,7 @@ runBtn.addEventListener('click', async () => { try { const src = editor.getValue() let wasm: Uint8Array | null + const tCompile = performance.now() try { wasm = await compile(src) } catch (err) { @@ -523,16 +524,26 @@ runBtn.addEventListener('click', async () => { outputEl.className = 'error' return } + const compileMs = performance.now() - tCompile if (!wasm) { outputEl.textContent = 'Compiler not available yet.' outputEl.className = 'error' return } const result = await run(wasm) - const time = `(${result.durationMs.toFixed(1)} ms)` - outputEl.textContent = result.ok - ? `OK — ${result.message} ${time}\n\n(stdout/stderr capture coming once interop/js.wat lands upstream)` + const parts = [ + `compile ${compileMs.toFixed(1)}ms`, + `init ${result.initMs.toFixed(1)}ms`, + `import ${result.importMs.toFixed(1)}ms`, + result.runMs !== null ? `run ${result.runMs.toFixed(1)}ms` : null, + ].filter(Boolean) + const time = `(${parts.join(' · ')})` + const head = result.ok + ? `OK — ${result.message} ${time}` : `${result.status}: ${result.message} ${time}` + const out = result.stdout.length ? `\n\nstdout:\n${result.stdout.join('')}` : '' + const err = result.stderr.length ? `\n\nstderr:\n${result.stderr.join('')}` : '' + outputEl.textContent = head + out + err outputEl.className = result.ok ? 'ok' : 'error' } catch (err) { outputEl.textContent = `Runtime error: ${err}` diff --git a/src/runner.ts b/src/runner.ts index 713af4f..a7b51ff 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -1,142 +1,78 @@ // Tier-1 ƒink WASM runner. // -// Instantiates a compiled fink module in the browser and calls its entry -// wrapper. Host imports are no-op stubs except `host_panic`, which throws — -// programs that compute pure results execute end-to-end; programs that try -// to do IO simply have their host calls swallowed for now. +// Instantiates a compiled fink module via the upstream JS host shim +// (src/fink.js) and imports the entry module. stdout/stderr writes from +// fink are captured via host overrides; the result of the module's last +// expression — or its `main` export, if present — is surfaced in the +// run summary. // -// Output is wall-clock duration + status + any trap message. Once fink -// gains a JS-friendly bytes-readback helper we'll surface stdout/stderr -// here too. +// Timing buckets (all milliseconds, all optional except init+import): +// initMs — init_wasm: WASM instantiation + host-shim wiring. +// importMs — fink.import('./playground.fnk'): module initialization. +// runMs — main(...) call (omitted if the module has no `main`). + +import { init_wasm } from './fink.js' export interface RunResult { ok: boolean - status: 'ok' | 'panic' | 'trap' | 'no-entry' + status: 'ok' | 'panic' | 'trap' message: string - durationMs: number -} - -interface HostImports { - env: { - host_panic: (...args: unknown[]) => void - host_channel_send: (...args: unknown[]) => void - host_read: (...args: unknown[]) => void - host_invoke_cont: (...args: unknown[]) => void - host_resume: (...args: unknown[]) => void - } -} - -function makeImports(): HostImports { - return { - env: { - host_panic: () => { - throw new Error('fink panic: irrefutable pattern failed') - }, - // No bytes-readback path yet — silently swallow. Once interop/js.wat - // lands upstream we can copy bytes out via a memory-readback helper. - host_channel_send: () => {}, - // No stdin source yet. The fink runtime expects host_read to settle - // a future asynchronously; a missing settle just leaves the program - // parked at that point. Programs that don't read stdin run cleanly. - host_read: () => {}, - // The cont handshake delivers main's exit code and module-init result. - // We discard both for now. - host_invoke_cont: () => {}, - // Scheduler reentry hook — the runtime calls this when the task - // queue is empty so the host can drive any pending IO / settle - // host futures and re-fill the queue. With no IO and no async - // futures, returning immediately is the correct behaviour: the - // scheduler will then exit because the queue is still empty. - host_resume: () => {}, - }, - } -} - -// Find the entry wrapper export. compile_package emits each module's -// wrapper under its canonical URL. The entry's URL is `./`. -function findEntryWrapper(instance: WebAssembly.Instance): { - name: string - func: Function -} | null { - const exports = instance.exports as Record - for (const name of Object.keys(exports)) { - if (!name.startsWith('./')) continue - const value = exports[name] - if (typeof value === 'function') { - return { name, func: value as Function } - } - } - return null -} - -/// Build an empty `$ByteArray` to pass as the wrapper's key. -/// -/// JS cannot directly construct a WasmGC array — there is no -/// `WebAssembly.Array.new()` API in any browser as of mid-2025. -/// -/// TODO(interop): only `interop/*.wat` exports are part of fink's -/// stable contract. The two `std/str.*` exports used here are internal -/// and *will* disappear under linker DCE / cleanup. Migrate to a -/// proper interop bootstrap export (e.g. an `interop/js.wat` -/// `_alloc_empty_bytes`) the moment one is available — currently -/// blocking the playground's tier-1 runner. -function makeEmptyByteArray(instance: WebAssembly.Instance): unknown { - const exports = instance.exports as Record - const strEmpty = exports['std/str.fnk:str_empty'] as Function | undefined - const bytes = exports['std/str.wat:bytes'] as Function | undefined - if (!strEmpty || !bytes) { - throw new Error('runtime missing str_empty / bytes exports — cannot bootstrap entry call') - } - return bytes(strEmpty()) -} - -function callEntry(entry: { name: string; func: Function }, key: unknown): void { - entry.func(key, 0) + initMs: number + importMs: number + runMs: number | null + stdout: string[] + stderr: string[] } export async function run(bytes: Uint8Array): Promise { - const t0 = performance.now() - let instance: WebAssembly.Instance + const stdout: string[] = [] + const stderr: string[] = [] + let initMs = 0 + let importMs = 0 + let runMs: number | null = null + try { - const result = await WebAssembly.instantiate(bytes, makeImports() as unknown as WebAssembly.Imports) - instance = result.instance - } catch (e) { - return { - ok: false, - status: 'trap', - message: `instantiate failed: ${(e as Error).message}`, - durationMs: performance.now() - t0, - } - } + const tInit = performance.now() + const fink = await init_wasm(bytes, { + stdout_write: (s: string) => stdout.push(s), + stderr_write: (s: string) => stderr.push(s), + panic: () => { throw new Error('fink panic: irrefutable pattern failed') }, + }) + initMs = performance.now() - tInit - const entry = findEntryWrapper(instance) - if (!entry) { - return { - ok: false, - status: 'no-entry', - message: 'no entry wrapper export (expected one starting with "./")', - durationMs: performance.now() - t0, + const tImport = performance.now() + const [last_val, mod] = await fink.import('./playground.fnk') + importMs = performance.now() - tImport + + let message = `last = ${formatLastVal(last_val)}` + if (mod && typeof (mod as { main?: unknown }).main === 'function') { + const main = (mod as { main: (arg: string) => Promise }).main + const tRun = performance.now() + const result = await main('playground') + runMs = performance.now() - tRun + message = `main → ${formatLastVal(result)}` } - } - try { - const key = makeEmptyByteArray(instance) - callEntry(entry, key) + return { ok: true, status: 'ok', message, initMs, importMs, runMs, stdout, stderr } } catch (e) { - const msg = (e as Error).message - const isPanic = msg.includes('fink panic') + const msg = (e as Error).message ?? String(e) + const isPanic = msg.includes('fink panic') || msg.includes('host_panic') return { ok: false, status: isPanic ? 'panic' : 'trap', message: msg, - durationMs: performance.now() - t0, + initMs, + importMs, + runMs, + stdout, + stderr, } } +} - return { - ok: true, - status: 'ok', - message: `ran ${entry.name}`, - durationMs: performance.now() - t0, - } +function formatLastVal(v: unknown): string { + if (v === undefined) return '()' + if (typeof v === 'string') return JSON.stringify(v) + if (typeof v === 'number' || typeof v === 'boolean') return String(v) + return String(v) }