wasm-idle 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +42 -32
  2. package/dist/clang/gccCompat.d.ts +15 -0
  3. package/dist/clang/gccCompat.js +767 -0
  4. package/dist/clang/index.js +5 -1
  5. package/dist/clang/url.d.ts +4 -4
  6. package/dist/clang/url.js +5 -4
  7. package/dist/clangd/session.d.ts +5 -1
  8. package/dist/clangd/session.js +22 -4
  9. package/dist/clangd/worker.js +32 -47
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/lsp/cpp/server.js +17 -6
  13. package/dist/lsp/index.d.ts +1 -1
  14. package/dist/lsp/index.js +1 -1
  15. package/dist/lsp/python/server.js +1 -1
  16. package/dist/lsp/runtime.d.ts +2 -0
  17. package/dist/lsp/runtime.js +9 -8
  18. package/dist/lsp/types.d.ts +2 -0
  19. package/dist/playground/assetBridge.js +8 -2
  20. package/dist/playground/assets.d.ts +5 -1
  21. package/dist/playground/assets.js +88 -47
  22. package/dist/playground/clang.d.ts +4 -2
  23. package/dist/playground/clang.js +35 -4
  24. package/dist/playground/ocaml.js +2 -0
  25. package/dist/playground/options.d.ts +4 -0
  26. package/dist/playground/tinygo.d.ts +4 -1
  27. package/dist/playground/tinygo.js +11 -6
  28. package/dist/playground/wasmOcamlVersion.d.ts +1 -1
  29. package/dist/playground/wasmOcamlVersion.js +1 -1
  30. package/dist/playground/wasmTinyGoVersion.d.ts +1 -1
  31. package/dist/playground/wasmTinyGoVersion.js +1 -1
  32. package/dist/playground/worker/clang.js +7 -2
  33. package/dist/playground/worker/elixir.js +296 -22
  34. package/dist/playground/worker/ocaml.js +5 -4
  35. package/dist/playground/worker/tinygo.js +7 -4
  36. package/dist/terminal/Terminal.svelte +4 -0
  37. package/dist/utils/vscodeJsonrpcBrowser.d.ts +5 -0
  38. package/dist/utils/vscodeJsonrpcBrowser.js +4 -0
  39. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { WorkerAssetBridge } from './assetBridge';
2
+ import { resolveRuntimeAssetConfig } from './assets';
1
3
  import { resolveSandboxExecutionArgs } from './options';
2
4
  import { bufferedSequence, flushBufferedEof, flushQueuedStdin, resetBufferedStdin, waitForBufferedSequenceChange } from './stdinBuffer';
3
5
  const debugBreakpointBufferInts = 1028;
@@ -20,23 +22,48 @@ class Clang {
20
22
  waitingForInput = false;
21
23
  pendingEof = false;
22
24
  exit = true;
25
+ assetBridge = null;
23
26
  activeReject = null;
24
27
  constructor(language) {
25
28
  this.language = language;
26
29
  }
27
- load(runtimeAssets = '', code = '', log = true, args = [], _options = {}, _progress) {
30
+ load(runtimeAssets = '', code = '', log = true, args = [], _options = {}, progress) {
28
31
  return new Promise(async (resolve) => {
29
32
  this.log = log;
30
33
  this.pendingInput = [];
31
34
  this.waitingForInput = false;
32
35
  this.pendingEof = false;
33
- const path = typeof runtimeAssets === 'string' ? runtimeAssets : runtimeAssets?.rootUrl || '';
36
+ const assetConfig = resolveRuntimeAssetConfig('clang', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '');
37
+ const needsWorkerReset = !this.worker || !this.assetBridge || !this.assetBridge.matches(assetConfig);
38
+ if (needsWorkerReset && this.worker) {
39
+ this.worker.terminate();
40
+ delete this.worker;
41
+ this.assetBridge = null;
42
+ }
34
43
  if (!this.worker) {
35
44
  this.worker = new (await import('./worker/clang?worker')).default();
36
- this.worker.onmessage = () => resolve();
37
- this.worker.postMessage({ load: true, path, log, code, args });
45
+ this.assetBridge = new WorkerAssetBridge(this.worker, 'clang', assetConfig, progress);
46
+ this.worker.onmessage = (event) => {
47
+ if (this.assetBridge?.handleMessage(event))
48
+ return;
49
+ if (event.data?.progress != null)
50
+ progress?.set?.(event.data.progress);
51
+ if (event.data?.load)
52
+ resolve();
53
+ };
54
+ this.worker.postMessage({
55
+ load: true,
56
+ log,
57
+ code,
58
+ args,
59
+ assets: {
60
+ baseUrl: assetConfig.baseUrl,
61
+ useAssetBridge: assetConfig.useAssetBridge
62
+ }
63
+ });
38
64
  }
39
65
  else {
66
+ this.assetBridge?.rebind(this.worker, assetConfig, progress);
40
67
  this.worker.postMessage({ log });
41
68
  resolve();
42
69
  }
@@ -74,6 +101,8 @@ class Clang {
74
101
  this.setBreakpoints(options.debug ? [...(options.breakpoints || [])] : []);
75
102
  const interrupt = new Uint8Array(this.interruptBuffer), _uid = ++this.uid;
76
103
  const handler = (event) => {
104
+ if (this.assetBridge?.handleMessage(event))
105
+ return;
77
106
  if (!this.worker)
78
107
  return reject('Worker not loaded');
79
108
  if (_uid !== this.uid)
@@ -180,6 +209,7 @@ class Clang {
180
209
  Atomics.notify(control, 0);
181
210
  this.worker?.terminate?.();
182
211
  delete this.worker;
212
+ this.assetBridge = null;
183
213
  this.exit = true;
184
214
  }
185
215
  async clear() {
@@ -198,6 +228,7 @@ class Clang {
198
228
  if (!this.exit) {
199
229
  this.worker?.terminate?.();
200
230
  delete this.worker;
231
+ this.assetBridge = null;
201
232
  this.exit = true;
202
233
  }
203
234
  }
@@ -96,6 +96,7 @@ class Ocaml {
96
96
  if (!this.worker)
97
97
  return reject('Worker not loaded');
98
98
  const target = options.ocamlBackend || 'wasm';
99
+ const wasmBinaryenMode = options.ocamlWasmBinaryenMode || 'fast';
99
100
  const _uid = ++this.uid;
100
101
  this.activeReject = reject;
101
102
  const handler = async (event) => {
@@ -308,6 +309,7 @@ class Ocaml {
308
309
  code,
309
310
  prepare,
310
311
  target,
312
+ wasmBinaryenMode,
311
313
  log: _log,
312
314
  buffer: this.buffer
313
315
  });
@@ -15,7 +15,9 @@ export interface CompilerDiagnostic {
15
15
  }
16
16
  export type RustTargetTriple = 'wasm32-wasip1' | 'wasm32-wasip2' | 'wasm32-wasip3';
17
17
  export type GoTarget = 'wasip1/wasm' | 'wasip2/wasm' | 'wasip3/wasm' | 'js/wasm';
18
+ export type TinyGoTarget = 'wasm' | 'wasip1' | 'wasip2' | 'wasip3';
18
19
  export type OcamlBackend = 'js' | 'wasm';
20
+ export type OcamlWasmBinaryenMode = 'fast' | 'full';
19
21
  export type DebugVariableKind = 'number' | 'bool' | 'array' | 'text';
20
22
  export type DebugArrayElementKind = 'int' | 'float' | 'double' | 'bool' | 'char';
21
23
  export interface DebugStructFieldMetadata {
@@ -62,7 +64,9 @@ export interface SandboxExecutionOptions {
62
64
  cVersion?: string;
63
65
  rustTargetTriple?: RustTargetTriple;
64
66
  goTarget?: GoTarget;
67
+ tinygoTarget?: TinyGoTarget;
65
68
  ocamlBackend?: OcamlBackend;
69
+ ocamlWasmBinaryenMode?: OcamlWasmBinaryenMode;
66
70
  }
67
71
  export interface ResolvedSandboxExecutionArgs {
68
72
  compileArgs: string[];
@@ -1,5 +1,5 @@
1
1
  import { type PlaygroundRuntimeAssets, type TinyGoRuntimeAssetLoader, type TinyGoRuntimeAssetPackReference } from './assets';
2
- import { type SandboxExecutionOptions } from './options';
2
+ import { type SandboxExecutionOptions, type TinyGoTarget } from './options';
3
3
  import type { Sandbox } from './sandbox';
4
4
  type TinyGoRuntimeHooks = {
5
5
  boot(): Promise<void>;
@@ -15,6 +15,9 @@ type TinyGoRuntimeHooks = {
15
15
  entrypoint?: '_start' | '_initialize' | 'main' | null;
16
16
  reason?: 'bootstrap-artifact' | 'missing-wasi-entrypoint';
17
17
  } | null;
18
+ setBuildRequestOverrides?(overrides: {
19
+ target?: TinyGoTarget;
20
+ } | null): void;
18
21
  setWorkspaceFiles(files: Record<string, string> | null): void;
19
22
  dispose?(): void;
20
23
  };
@@ -146,7 +146,8 @@ class TinyGo {
146
146
  }
147
147
  const moduleUrl = this.moduleUrl;
148
148
  this.runtimePromise = (async () => {
149
- const runtimeModule = (await import(/* @vite-ignore */ moduleUrl));
149
+ const runtimeModule = (await import(
150
+ /* @vite-ignore */ moduleUrl));
150
151
  if (typeof runtimeModule.createBundledTinyGoRuntime === 'function') {
151
152
  this.runtime = runtimeModule.createBundledTinyGoRuntime({
152
153
  assetLoader: this.assetLoader,
@@ -212,7 +213,6 @@ class TinyGo {
212
213
  });
213
214
  return this.runtime;
214
215
  }
215
- ;
216
216
  throw new Error('TinyGo runtime module must export createBundledTinyGoRuntime or createTinyGoRuntime');
217
217
  })().catch((error) => {
218
218
  this.disposeRuntime();
@@ -245,11 +245,13 @@ class TinyGo {
245
245
  }
246
246
  return 'TinyGo compilation failed';
247
247
  }
248
- async compileArtifact(code, log = true, prog) {
248
+ async compileArtifact(code, target = 'wasm', log = true, prog) {
249
+ const hostCompileTarget = target === 'wasm' ? 'wasip1' : target;
249
250
  const compileCacheKey = JSON.stringify({
250
251
  code,
251
252
  hostCompileUrls: this.hostCompileUrls,
252
- moduleUrl: this.moduleUrl
253
+ moduleUrl: this.moduleUrl,
254
+ target
253
255
  });
254
256
  if (this.compiledArtifact && this.compiledCacheKey === compileCacheKey) {
255
257
  return;
@@ -266,7 +268,8 @@ class TinyGo {
266
268
  'content-type': 'application/json'
267
269
  },
268
270
  body: JSON.stringify({
269
- source: code
271
+ source: code,
272
+ target: hostCompileTarget
270
273
  })
271
274
  });
272
275
  }
@@ -337,6 +340,7 @@ class TinyGo {
337
340
  runtime.reset();
338
341
  this.lastActivityLog = runtime.readActivityLog();
339
342
  runtime.setWorkspaceFiles({ 'main.go': code });
343
+ runtime.setBuildRequestOverrides?.({ target });
340
344
  this.runtimeProgress = prog;
341
345
  this.runtimeProgressAssets.clear();
342
346
  this.runtimeProgressStart = 0.05;
@@ -420,7 +424,8 @@ class TinyGo {
420
424
  try {
421
425
  this.begin = Date.now();
422
426
  await this.ensureWorker();
423
- await this.compileArtifact(code, _log, prepare ? _prog : undefined);
427
+ const target = options.tinygoTarget || 'wasm';
428
+ await this.compileArtifact(code, target, _log, prepare ? _prog : undefined);
424
429
  if (prepare) {
425
430
  this.elapse = Date.now() - this.begin;
426
431
  this.exit = true;
@@ -1 +1 @@
1
- export declare const WASM_OCAML_ASSET_VERSION = "bd4c85620ead3426";
1
+ export declare const WASM_OCAML_ASSET_VERSION = "5215e843db69fc28";
@@ -1 +1 @@
1
- export const WASM_OCAML_ASSET_VERSION = "bd4c85620ead3426";
1
+ export const WASM_OCAML_ASSET_VERSION = "5215e843db69fc28";
@@ -1 +1 @@
1
- export declare const WASM_TINYGO_ASSET_VERSION = "c45e1d6b3e297250";
1
+ export declare const WASM_TINYGO_ASSET_VERSION = "be561e38c81e2e23";
@@ -1 +1 @@
1
- export const WASM_TINYGO_ASSET_VERSION = "c45e1d6b3e297250";
1
+ export const WASM_TINYGO_ASSET_VERSION = 'be561e38c81e2e23';
@@ -1,4 +1,5 @@
1
1
  import { waitForBufferedStdin } from '../stdinBuffer';
2
+ import { configureWorkerRuntimeAssets, handleWorkerAssetMessage } from './assets';
2
3
  self.document = {
3
4
  querySelectorAll() {
4
5
  return [];
@@ -17,9 +18,13 @@ async function loadClang(path, log) {
17
18
  });
18
19
  }
19
20
  self.onmessage = async (event) => {
20
- const { code, buffer, debugBuffer, watchBuffer, watchResultBuffer, load, interrupt, log, path, prepare, language, compileArgs, programArgs, cppVersion, cVersion, debug, breakpoints, pauseOnEntry } = event.data;
21
+ if (handleWorkerAssetMessage(event.data))
22
+ return;
23
+ const { code, buffer, debugBuffer, watchBuffer, watchResultBuffer, load, interrupt, log, path, assets, prepare, language, compileArgs, programArgs, cppVersion, cVersion, debug, breakpoints, pauseOnEntry } = event.data;
21
24
  if (load) {
22
- await loadClang(path, log);
25
+ const runtimeAssets = assets;
26
+ configureWorkerRuntimeAssets(runtimeAssets || null);
27
+ await loadClang(runtimeAssets?.baseUrl || path || '', log);
23
28
  postMessage({ load: true });
24
29
  }
25
30
  else if (prepare) {
@@ -1,5 +1,3 @@
1
- import atomVmWasmUrl from '../../../../node_modules/@swmansion/popcorn/dist/AtomVM.wasm?url';
2
- import initAtomVm from '../../../../node_modules/@swmansion/popcorn/dist/AtomVM.mjs';
3
1
  import { waitForBufferedStdin } from '../stdinBuffer';
4
2
  const workerDocument = {
5
3
  querySelectorAll() {
@@ -33,6 +31,130 @@ let bundleUrl = '';
33
31
  let loadedBundleUrl = '';
34
32
  let runtimePromise = null;
35
33
  let stdinBufferElixir = null;
34
+ const elixirStdinCallNames = [
35
+ 'IO.gets',
36
+ 'IO.read',
37
+ 'IO.binread',
38
+ 'IO.getn',
39
+ ':io.get_line',
40
+ ':io.get_chars'
41
+ ];
42
+ function resolveElixirRuntimeAssetUrl(assetName, bundleAssetUrl) {
43
+ const absoluteBundleUrl = new URL(bundleAssetUrl.startsWith('/') ? `http://localhost${bundleAssetUrl}` : bundleAssetUrl, globalThis.location?.href || 'http://localhost/');
44
+ return new URL(assetName, absoluteBundleUrl).toString();
45
+ }
46
+ async function resolveAtomVmInitializer(moduleAssetUrl) {
47
+ const injectedInitializer = globalThis.__wasmIdleAtomVmInit;
48
+ if (typeof injectedInitializer === 'function') {
49
+ return injectedInitializer;
50
+ }
51
+ const module = (await import(/* @vite-ignore */ moduleAssetUrl));
52
+ return module.default;
53
+ }
54
+ function skipQuotedLiteral(source, start) {
55
+ const heredoc = source.slice(start, start + 3);
56
+ if (heredoc === '"""' || heredoc === "'''") {
57
+ const end = source.indexOf(heredoc, start + 3);
58
+ return end === -1 ? source.length : end + 3;
59
+ }
60
+ const quote = source[start];
61
+ let index = start + 1;
62
+ while (index < source.length) {
63
+ if (source[index] === '\\') {
64
+ index += 2;
65
+ continue;
66
+ }
67
+ if (source[index] === quote) {
68
+ return index + 1;
69
+ }
70
+ index += 1;
71
+ }
72
+ return source.length;
73
+ }
74
+ function findClosingParen(source, openParenIndex) {
75
+ let depth = 1;
76
+ let index = openParenIndex + 1;
77
+ while (index < source.length) {
78
+ const current = source[index];
79
+ if (current === '"' || current === "'") {
80
+ index = skipQuotedLiteral(source, index);
81
+ continue;
82
+ }
83
+ if (current === '#') {
84
+ const newlineIndex = source.indexOf('\n', index);
85
+ if (newlineIndex === -1) {
86
+ return -1;
87
+ }
88
+ index = newlineIndex + 1;
89
+ continue;
90
+ }
91
+ if (current === '(') {
92
+ depth += 1;
93
+ }
94
+ else if (current === ')') {
95
+ depth -= 1;
96
+ if (depth === 0) {
97
+ return index;
98
+ }
99
+ }
100
+ index += 1;
101
+ }
102
+ return -1;
103
+ }
104
+ function splitTopLevelArgs(source) {
105
+ const parts = [];
106
+ let start = 0;
107
+ let parenDepth = 0;
108
+ let braceDepth = 0;
109
+ let bracketDepth = 0;
110
+ let index = 0;
111
+ while (index < source.length) {
112
+ const current = source[index];
113
+ if (current === '"' || current === "'") {
114
+ index = skipQuotedLiteral(source, index);
115
+ continue;
116
+ }
117
+ if (current === '#') {
118
+ const newlineIndex = source.indexOf('\n', index);
119
+ if (newlineIndex === -1)
120
+ break;
121
+ index = newlineIndex + 1;
122
+ continue;
123
+ }
124
+ if (current === '(')
125
+ parenDepth += 1;
126
+ if (current === ')')
127
+ parenDepth -= 1;
128
+ if (current === '{')
129
+ braceDepth += 1;
130
+ if (current === '}')
131
+ braceDepth -= 1;
132
+ if (current === '[')
133
+ bracketDepth += 1;
134
+ if (current === ']')
135
+ bracketDepth -= 1;
136
+ if (current === ',' && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) {
137
+ parts.push(source.slice(start, index).trim());
138
+ start = index + 1;
139
+ }
140
+ index += 1;
141
+ }
142
+ const trailing = source.slice(start).trim();
143
+ if (trailing) {
144
+ parts.push(trailing);
145
+ }
146
+ return parts;
147
+ }
148
+ function parseLiteralCount(source) {
149
+ const normalized = source.trim().replaceAll('_', '');
150
+ if (/^\d+$/.test(normalized)) {
151
+ const parsed = Number.parseInt(normalized, 10);
152
+ if (Number.isSafeInteger(parsed)) {
153
+ return parsed;
154
+ }
155
+ }
156
+ return null;
157
+ }
36
158
  function deserialize(module, message) {
37
159
  return JSON.parse(message, (_key, value) => {
38
160
  const isRef = typeof value === 'object' &&
@@ -146,7 +268,7 @@ function configureMessagingBridge(module) {
146
268
  };
147
269
  module.call = (process, args) => originalCall(process, module.serialize(args));
148
270
  }
149
- async function startVm(avmBundle, log) {
271
+ async function startVm(avmBundle, bundleAssetUrl, log) {
150
272
  let resolveProcess = null;
151
273
  const processPromise = new Promise((resolve, reject) => {
152
274
  const timeout = setTimeout(() => {
@@ -159,6 +281,9 @@ async function startVm(avmBundle, log) {
159
281
  resolve(process);
160
282
  };
161
283
  });
284
+ const atomVmModuleUrl = resolveElixirRuntimeAssetUrl('AtomVM.mjs', bundleAssetUrl);
285
+ const atomVmWasmUrl = resolveElixirRuntimeAssetUrl('AtomVM.wasm', bundleAssetUrl);
286
+ const initAtomVm = await resolveAtomVmInitializer(atomVmModuleUrl);
162
287
  const module = (await initAtomVm({
163
288
  locateFile(path) {
164
289
  if (path === 'AtomVM.wasm') {
@@ -211,7 +336,7 @@ async function loadRuntime(nextBundleUrl, log) {
211
336
  }
212
337
  return response.arrayBuffer();
213
338
  });
214
- return await startVm(new Int8Array(bundleBuffer), log);
339
+ return await startVm(new Int8Array(bundleBuffer), nextBundleUrl, log);
215
340
  })();
216
341
  return await runtimePromise;
217
342
  }
@@ -265,24 +390,173 @@ self.onmessage = async (event) => {
265
390
  console.log(`[wasm-idle:elixir-worker] eval bytes=${code.length}`);
266
391
  }
267
392
  let evalCode = code;
268
- if (stdinBufferElixir && /\bIO\.(?:gets|read)\s*\(/.test(code)) {
269
- const chunk = waitForBufferedStdin(stdinBufferElixir, () => postMessage({ buffer: true }));
270
- const stdinLines = chunk == null ? [] : chunk.match(/[^\n]*\n|[^\n]+$/g) || [];
271
- let stdinLineOffset = 0;
272
- evalCode = code
273
- .replace(/\bIO\.gets\s*\([^)]*\)/g, () => {
274
- const line = stdinLines[stdinLineOffset++];
275
- return line === undefined ? 'nil' : JSON.stringify(line);
276
- })
277
- .replace(/\bIO\.read\s*\(\s*(?::stdio\s*,\s*)?:line\s*\)/g, () => {
278
- const line = stdinLines[stdinLineOffset++];
279
- return line === undefined ? 'nil' : JSON.stringify(line);
280
- })
281
- .replace(/\bIO\.read\s*\(\s*(?::stdio\s*,\s*)?:all\s*\)/g, () => {
282
- const remainingInput = stdinLines.slice(stdinLineOffset).join('');
283
- stdinLineOffset = stdinLines.length;
284
- return JSON.stringify(remainingInput);
285
- });
393
+ if (stdinBufferElixir && /(?:\bIO\.|\:io\.)/.test(code)) {
394
+ let bufferedInput = '';
395
+ let reachedEof = false;
396
+ const pullStdinChunk = () => {
397
+ if (reachedEof)
398
+ return false;
399
+ const nextChunk = waitForBufferedStdin(stdinBufferElixir, () => postMessage({ buffer: true }));
400
+ if (nextChunk === null) {
401
+ reachedEof = true;
402
+ return false;
403
+ }
404
+ bufferedInput += nextChunk;
405
+ return true;
406
+ };
407
+ const readLineLiteral = (eofLiteral) => {
408
+ while (true) {
409
+ const newlineIndex = bufferedInput.indexOf('\n');
410
+ if (newlineIndex !== -1) {
411
+ const line = bufferedInput.slice(0, newlineIndex + 1);
412
+ bufferedInput = bufferedInput.slice(newlineIndex + 1);
413
+ return JSON.stringify(line);
414
+ }
415
+ if (!pullStdinChunk()) {
416
+ if (!bufferedInput) {
417
+ return eofLiteral;
418
+ }
419
+ const remainder = bufferedInput;
420
+ bufferedInput = '';
421
+ return JSON.stringify(remainder);
422
+ }
423
+ }
424
+ };
425
+ const readCountLiteral = (count, eofLiteral) => {
426
+ if (count === 0) {
427
+ return JSON.stringify('');
428
+ }
429
+ while (bufferedInput.length < count && pullStdinChunk()) {
430
+ // Keep requesting chunks until the requested count or EOF is available.
431
+ }
432
+ if (!bufferedInput) {
433
+ return eofLiteral;
434
+ }
435
+ const consumed = bufferedInput.slice(0, Math.min(count, bufferedInput.length));
436
+ bufferedInput = bufferedInput.slice(consumed.length);
437
+ return JSON.stringify(consumed);
438
+ };
439
+ const readAllLiteral = (eofLiteral) => {
440
+ while (pullStdinChunk()) {
441
+ // Drain stdin until EOF for :all reads.
442
+ }
443
+ if (!bufferedInput) {
444
+ return eofLiteral;
445
+ }
446
+ const consumed = bufferedInput;
447
+ bufferedInput = '';
448
+ return JSON.stringify(consumed);
449
+ };
450
+ const wrapWithDeviceEvaluation = (deviceArg, valueExpr) => deviceArg
451
+ ? `((fn __wasm_idle_device__ -> _ = __wasm_idle_device__; ${valueExpr} end).(${deviceArg}))`
452
+ : valueExpr;
453
+ const wrapWithPromptOutput = (promptArg, valueExpr, deviceArg) => deviceArg
454
+ ? `((fn __wasm_idle_device__, __wasm_idle_prompt__ -> _ = __wasm_idle_device__; IO.write(__wasm_idle_prompt__); ${valueExpr} end).(${deviceArg}, ${promptArg}))`
455
+ : `((fn __wasm_idle_prompt__ -> IO.write(__wasm_idle_prompt__); ${valueExpr} end).(${promptArg}))`;
456
+ let rewritten = '';
457
+ let cursor = 0;
458
+ while (cursor < code.length) {
459
+ const current = code[cursor];
460
+ if (current === '"' || current === "'") {
461
+ const nextCursor = skipQuotedLiteral(code, cursor);
462
+ rewritten += code.slice(cursor, nextCursor);
463
+ cursor = nextCursor;
464
+ continue;
465
+ }
466
+ if (current === '#') {
467
+ const newlineIndex = code.indexOf('\n', cursor);
468
+ if (newlineIndex === -1) {
469
+ rewritten += code.slice(cursor);
470
+ break;
471
+ }
472
+ rewritten += code.slice(cursor, newlineIndex + 1);
473
+ cursor = newlineIndex + 1;
474
+ continue;
475
+ }
476
+ const callName = elixirStdinCallNames.find((candidate) => {
477
+ if (!code.startsWith(candidate, cursor)) {
478
+ return false;
479
+ }
480
+ const previous = code[cursor - 1] || '';
481
+ return !/[A-Za-z0-9_:.?!]/.test(previous);
482
+ });
483
+ if (!callName) {
484
+ rewritten += current;
485
+ cursor += 1;
486
+ continue;
487
+ }
488
+ let openParenIndex = cursor + callName.length;
489
+ while (/\s/.test(code[openParenIndex] || '')) {
490
+ openParenIndex += 1;
491
+ }
492
+ if (code[openParenIndex] !== '(') {
493
+ rewritten += current;
494
+ cursor += 1;
495
+ continue;
496
+ }
497
+ const closeParenIndex = findClosingParen(code, openParenIndex);
498
+ if (closeParenIndex === -1) {
499
+ rewritten += code.slice(cursor);
500
+ break;
501
+ }
502
+ const originalCall = code.slice(cursor, closeParenIndex + 1);
503
+ const args = splitTopLevelArgs(code.slice(openParenIndex + 1, closeParenIndex));
504
+ let replacement = originalCall;
505
+ if (callName === 'IO.gets' && (args.length === 1 || args.length === 2)) {
506
+ replacement =
507
+ args.length === 2
508
+ ? wrapWithPromptOutput(args[1], readLineLiteral('nil'), args[0])
509
+ : wrapWithPromptOutput(args[0], readLineLiteral('nil'));
510
+ }
511
+ else if ((callName === 'IO.read' || callName === 'IO.binread') &&
512
+ (args.length === 1 || args.length === 2)) {
513
+ const deviceArg = args.length === 2 ? args[0] : undefined;
514
+ const modeArg = args[args.length - 1];
515
+ if (modeArg === ':line') {
516
+ replacement = wrapWithDeviceEvaluation(deviceArg, readLineLiteral(':eof'));
517
+ }
518
+ else if (modeArg === ':all') {
519
+ replacement = wrapWithDeviceEvaluation(deviceArg, readAllLiteral(':eof'));
520
+ }
521
+ else {
522
+ const count = parseLiteralCount(modeArg);
523
+ if (count === null) {
524
+ throw new Error(`${callName} stdin bridge requires a literal non-negative count`);
525
+ }
526
+ replacement = wrapWithDeviceEvaluation(deviceArg, readCountLiteral(count, ':eof'));
527
+ }
528
+ }
529
+ else if (callName === 'IO.getn' && (args.length === 2 || args.length === 3)) {
530
+ const count = parseLiteralCount(args[args.length - 1]);
531
+ if (count === null) {
532
+ throw new Error('IO.getn stdin bridge requires a literal non-negative count');
533
+ }
534
+ replacement =
535
+ args.length === 3
536
+ ? wrapWithPromptOutput(args[1], readCountLiteral(count, ':eof'), args[0])
537
+ : wrapWithPromptOutput(args[0], readCountLiteral(count, ':eof'));
538
+ }
539
+ else if (callName === ':io.get_line' && (args.length === 1 || args.length === 2)) {
540
+ replacement =
541
+ args.length === 2
542
+ ? wrapWithPromptOutput(args[1], readLineLiteral(':eof'), args[0])
543
+ : wrapWithPromptOutput(args[0], readLineLiteral(':eof'));
544
+ }
545
+ else if (callName === ':io.get_chars' &&
546
+ (args.length === 2 || args.length === 3)) {
547
+ const count = parseLiteralCount(args[args.length - 1]);
548
+ if (count === null) {
549
+ throw new Error(':io.get_chars stdin bridge requires a literal non-negative count');
550
+ }
551
+ replacement =
552
+ args.length === 3
553
+ ? wrapWithPromptOutput(args[1], readCountLiteral(count, ':eof'), args[0])
554
+ : wrapWithPromptOutput(args[0], readCountLiteral(count, ':eof'));
555
+ }
556
+ rewritten += replacement;
557
+ cursor = closeParenIndex + 1;
558
+ }
559
+ evalCode = rewritten;
286
560
  }
287
561
  const response = await runtime.module.call(runtime.process, ['eval_elixir', evalCode]);
288
562
  const rendered = runtime.module.deserialize(response);
@@ -363,7 +363,7 @@ async function executeCompileResult(result, log = false) {
363
363
  }
364
364
  }
365
365
  self.onmessage = async (event) => {
366
- const { load, moduleUrl: nextModuleUrl, manifestUrl: nextManifestUrl, code, prepare, target = 'wasm', log = true, buffer } = event.data;
366
+ const { load, moduleUrl: nextModuleUrl, manifestUrl: nextManifestUrl, code, prepare, target = 'wasm', wasmBinaryenMode = 'fast', log = true, buffer } = event.data;
367
367
  try {
368
368
  if (load) {
369
369
  moduleUrl = nextModuleUrl;
@@ -374,7 +374,7 @@ self.onmessage = async (event) => {
374
374
  }
375
375
  stdinBufferOcaml = buffer ? new Int32Array(buffer) : null;
376
376
  if (log) {
377
- console.log(`[wasm-idle:ocaml-worker] compile start prepare=${prepare} target=${target}`);
377
+ console.log(`[wasm-idle:ocaml-worker] compile start prepare=${prepare} target=${target} binaryen=${wasmBinaryenMode}`);
378
378
  }
379
379
  postMessage({ progress: { stage: 'compile-bootstrap', percent: 10 } });
380
380
  const [compilerModule, manifest] = await Promise.all([
@@ -382,7 +382,7 @@ self.onmessage = async (event) => {
382
382
  loadManifest(manifestUrl)
383
383
  ]);
384
384
  postMessage({ progress: { stage: 'compile-ready', percent: 25 } });
385
- const compileKey = `${target}\n${code}`;
385
+ const compileKey = `${target}\n${wasmBinaryenMode}\n${code}`;
386
386
  let compiledFresh = false;
387
387
  if (!compiledResult || compiledCacheKey !== compileKey) {
388
388
  const result = await compilerModule.compile({
@@ -391,7 +391,8 @@ self.onmessage = async (event) => {
391
391
  },
392
392
  entry: 'main.ml',
393
393
  target,
394
- effectsMode: 'cps'
394
+ effectsMode: 'cps',
395
+ wasmBinaryenMode
395
396
  }, {
396
397
  system: compilerModule.createBrowserWorkerSystemDispatcher({
397
398
  manifest
@@ -99,17 +99,20 @@ self.onmessage = async (event) => {
99
99
  exitCode = wasiRuntime.start(instance);
100
100
  }
101
101
  else if (typeof exportsObject.memory === 'object' &&
102
- typeof exportsObject.main === 'function') {
102
+ (typeof exportsObject.main === 'function' ||
103
+ typeof exportsObject._initialize === 'function')) {
103
104
  wasiRuntime.initialize(instance);
104
105
  if (typeof exportsObject._initialize !== 'function' &&
105
106
  typeof exportsObject.__wasm_call_ctors === 'function') {
106
107
  exportsObject.__wasm_call_ctors();
107
108
  }
108
- const mainResult = exportsObject.main();
109
- exitCode = typeof mainResult === 'number' ? mainResult : 0;
109
+ if (typeof exportsObject.main === 'function') {
110
+ const mainResult = exportsObject.main();
111
+ exitCode = typeof mainResult === 'number' ? mainResult : 0;
112
+ }
110
113
  }
111
114
  else {
112
- throw new Error('TinyGo worker expected a WASI artifact with _start or a reactor artifact with main');
115
+ throw new Error('TinyGo worker expected a WASI artifact with _start or a reactor artifact with _initialize or main');
113
116
  }
114
117
  }
115
118
  catch (error) {
@@ -107,6 +107,10 @@
107
107
  hasPythonLoader: !!currentRuntimeAssets?.python?.loader,
108
108
  javaBaseUrl: currentRuntimeAssets?.java?.baseUrl || '',
109
109
  hasJavaLoader: !!currentRuntimeAssets?.java?.loader,
110
+ clangBaseUrl: currentRuntimeAssets?.clang?.baseUrl || '',
111
+ hasClangLoader: !!currentRuntimeAssets?.clang?.loader,
112
+ clangdBaseUrl: currentRuntimeAssets?.clangd?.baseUrl || '',
113
+ hasClangdLoader: !!currentRuntimeAssets?.clangd?.loader,
110
114
  rustCompilerUrl: currentRuntimeAssets?.rust?.compilerUrl || '',
111
115
  goCompilerUrl: currentRuntimeAssets?.go?.compilerUrl || '',
112
116
  elixirBundleUrl: currentRuntimeAssets?.elixir?.bundleUrl || '',