diff --git a/crate/Cargo.lock b/crate/Cargo.lock index eba07ba..25dd85c 100644 --- a/crate/Cargo.lock +++ b/crate/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "bitflags" version = "2.11.0" @@ -29,10 +35,11 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fink" version = "0.0.0" -source = "git+https://github.com/fink-lang/fink.git?tag=v0.1.0#1a61a6b21aa0ad51c2f03e4062df64f774a17ae1" +source = "git+https://github.com/fink-lang/fink.git?tag=v0.3.0#f7ba307c4d1a1f05688decb36c7d8404cfc86cfd" dependencies = [ "wasm-encoder", "wasmparser", + "wasmprinter", ] [[package]] @@ -154,6 +161,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -227,3 +243,38 @@ dependencies = [ "semver", "serde", ] + +[[package]] +name = "wasmprinter" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/crate/Cargo.toml b/crate/Cargo.toml index 09b2a97..1d683bb 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.1.0", default-features = false } +fink = { git = "https://github.com/fink-lang/fink.git", tag = "v0.3.0", default-features = false, features = ["compiler", "wat"] } wasm-bindgen = "0.2" [profile.release] diff --git a/crate/src/lib.rs b/crate/src/lib.rs index 8b21155..c50f1d8 100644 --- a/crate/src/lib.rs +++ b/crate/src/lib.rs @@ -664,6 +664,28 @@ fn serialize_children(node: &ast::Node) -> String { parts.join(",") } +// --------------------------------------------------------------------------- +// Compiler: Fink source → WASM binary +// --------------------------------------------------------------------------- + +/// Compile Fink source to a WASM binary. +/// Returns the raw bytes on success, or throws a JS error on failure. +#[wasm_bindgen] +pub fn compile(src: &str) -> Result, JsValue> { + use fink::ast::build_index; + use fink::parser::parse; + use fink::passes::closure_lifting::lift_all; + use fink::passes::cps::transform::lower_expr; + use fink::passes::wasm::codegen::codegen; + + let r = parse(src).map_err(|e| JsValue::from_str(&e.message))?; + let ast_index = build_index(&r); + let cps = lower_expr(&r.root); + let (lifted, resolved) = lift_all(cps, &ast_index); + let result = codegen(&lifted, &resolved, &ast_index); + Ok(result.wasm) +} + // --------------------------------------------------------------------------- /// Stateful parsed document - parse once, query many times. diff --git a/src/compiler.ts b/src/compiler.ts index a11ef38..44a6748 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,6 +1,16 @@ -// Placeholder compiler — real Fink→WASM codegen is not ready yet. -// Returns null until a compile(src) → Uint8Array entry point is available. +// Fink → WASM compiler. The compile() function is backed by the playground +// WASM crate (fink_playground_wasm). Call setCompileModule() once the crate +// is initialised (see main.ts) so compile() can delegate to it. -export async function compile(_src: string): Promise { - return null +type WasmModule = { compile: (src: string) => Uint8Array } + +let wasmMod: WasmModule | null = null + +export function setCompileModule(mod: WasmModule): void { + wasmMod = mod +} + +export async function compile(src: string): Promise { + if (!wasmMod) return null + return wasmMod.compile(src) } diff --git a/src/main.ts b/src/main.ts index c039153..61a99c9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,8 +6,8 @@ // Analysis (semantic tokens, diagnostics, go-to-def, references) uses the // playground WASM crate loaded via dynamic import at runtime. // -// Code execution uses the WASI shim (wasi-shim.ts) running in a sandboxed -// iframe. The compiler slot is a placeholder for now (see compiler.ts). +// Code execution: compile(src) → WASM binary via the playground crate's +// compile() export, then run in a sandboxed WASI iframe (wasi-shim.ts). // MonacoEnvironment must be set before the editor creates its workers. ;(window as any).MonacoEnvironment = { @@ -34,7 +34,7 @@ import 'monaco-editor/esm/vs/editor/contrib/cursorUndo/browser/cursorUndo.js' import 'monaco-editor/esm/vs/editor/contrib/hover/browser/hoverContribution.js' // hover tooltips (diagnostics, symbols) import 'monaco-editor/esm/vs/editor/contrib/gotoSymbol/browser/goToCommands.js' // go-to-definition (F12 / Cmd+click) import 'monaco-editor/esm/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.js' // Ctrl/Cmd+click inline -import { compile } from './compiler.js' +import { compile, setCompileModule } from './compiler.js' import { run } from './wasi-shim.js' import { FinkTokenizer, type LexToken } from './tokenizer.js' import { TokensPanel } from './tokens-panel.js' @@ -153,6 +153,7 @@ async function loadAnalysisWasm(): Promise { console.log('[fink] glue module imported, calling init...') await mod.default(wasmBin) ParsedDocument = mod.ParsedDocument + setCompileModule(mod) resolveWasmReady() console.log('[fink] analysis WASM ready') @@ -496,19 +497,25 @@ runBtn.addEventListener('click', async () => { try { const src = editor.getValue() - const wasm = await compile(src) + let wasm: Uint8Array | null + try { + wasm = await compile(src) + } catch (err) { + outputEl.textContent = `Compile error: ${err}` + outputEl.className = 'error' + return + } if (!wasm) { outputEl.textContent = 'Compiler not available yet.' outputEl.className = 'error' return } const result = await run(wasm) - const text = result.stdout + result.stderr outputEl.textContent = text || '(no output)' outputEl.className = result.exitCode === 0 ? 'ok' : 'error' } catch (err) { - outputEl.textContent = `Error: ${err}` + outputEl.textContent = `Runtime error: ${err}` outputEl.className = 'error' } finally { runBtn.disabled = false diff --git a/src/wasi-shim.ts b/src/wasi-shim.ts index 316a5f5..a9b5507 100644 --- a/src/wasi-shim.ts +++ b/src/wasi-shim.ts @@ -1,9 +1,10 @@ -// Minimal WASI preview1 shim that runs compiled WASM inside a sandboxed -// iframe and relays stdout/stderr back to the parent via postMessage. +// Fink WASM runner. // -// Implements only the syscalls needed by typical Fink output: -// fd_write, proc_exit, fd_close, fd_seek, -// environ_sizes_get, environ_get, args_sizes_get, args_get +// The fink codegen produces a standalone module (no WASI imports) that exports: +// fink_main — entry point (no params, no results) +// result — global i32 holding the final value written by $__halt +// +// TODO: swap to WASI preview1 once the compiler supports IO. export interface RunResult { stdout: string @@ -11,107 +12,17 @@ export interface RunResult { exitCode: number } -// The runner script embedded in the iframe via srcdoc. -// The closing tag is intentionally split across the template -// literal and the string concatenation so the HTML parser does not -// interpret it as the end of the ` - -export function run(wasm: Uint8Array): Promise { - return new Promise((resolve, reject) => { - const iframe = document.createElement('iframe') - iframe.style.display = 'none' - iframe.setAttribute('sandbox', 'allow-scripts') - document.body.appendChild(iframe) - - let stdout = '' - let stderr = '' - - function cleanup(result?: RunResult, err?: Error) { - window.removeEventListener('message', onMessage) - document.body.removeChild(iframe) - if (err) reject(err) - else resolve(result!) - } - - function onMessage(e: MessageEvent) { - if (e.source !== iframe.contentWindow) return - const { data } = e - - if (data.type === 'ready') { - // Structured-clone the buffer so the original Uint8Array stays usable. - iframe.contentWindow!.postMessage({ type: 'run', wasm: wasm.buffer }, '*') - } else if (data.type === 'output') { - if (data.fd === 1) stdout += data.text - else if (data.fd === 2) stderr += data.text - } else if (data.type === 'done') { - cleanup({ stdout, stderr, exitCode: data.exitCode }) - } else if (data.type === 'error') { - cleanup(undefined, new Error(data.message)) - } - } - - window.addEventListener('message', onMessage) - iframe.srcdoc = RUNNER_HTML - }) + const value = exports.result.value as number + return { stdout: String(value), stderr: '', exitCode: 0 } }