wasm-idle 0.6.1 → 0.7.1
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.
- package/README.md +77 -11
- package/dist/clangd/session.js +1 -1
- package/dist/clangd/worker.js +1 -1
- package/dist/lsp/cpp/server.js +1 -1
- package/dist/lsp/python/server.js +1 -1
- package/dist/playground/assets.d.ts +43 -0
- package/dist/playground/assets.js +164 -0
- package/dist/playground/elixir.d.ts +32 -0
- package/dist/playground/elixir.js +173 -0
- package/dist/playground/go.d.ts +31 -0
- package/dist/playground/go.js +166 -0
- package/dist/playground/index.js +36 -1
- package/dist/playground/ocaml.d.ts +32 -0
- package/dist/playground/ocaml.js +341 -0
- package/dist/playground/options.d.ts +4 -0
- package/dist/playground/tinygo.d.ts +75 -0
- package/dist/playground/tinygo.js +510 -0
- package/dist/playground/wasmElixirVersion.d.ts +1 -0
- package/dist/playground/wasmElixirVersion.js +1 -0
- package/dist/playground/wasmGoVersion.d.ts +1 -0
- package/dist/playground/wasmGoVersion.js +1 -0
- package/dist/playground/wasmOcamlVersion.d.ts +1 -0
- package/dist/playground/wasmOcamlVersion.js +1 -0
- package/dist/playground/wasmRustVersion.d.ts +1 -1
- package/dist/playground/wasmRustVersion.js +1 -1
- package/dist/playground/wasmTinyGoVersion.d.ts +1 -0
- package/dist/playground/wasmTinyGoVersion.js +1 -0
- package/dist/playground/worker/elixir.d.ts +1 -0
- package/dist/playground/worker/elixir.js +591 -0
- package/dist/playground/worker/go.d.ts +1 -0
- package/dist/playground/worker/go.js +163 -0
- package/dist/playground/worker/ocaml.d.ts +1 -0
- package/dist/playground/worker/ocaml.js +449 -0
- package/dist/playground/worker/tinygo.d.ts +1 -0
- package/dist/playground/worker/tinygo.js +140 -0
- package/dist/terminal/Terminal.svelte +69 -29
- package/dist/utils/vscodeJsonrpcBrowser.d.ts +5 -0
- package/dist/utils/vscodeJsonrpcBrowser.js +4 -0
- package/package.json +33 -21
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
2
|
+
const workerDocument = {
|
|
3
|
+
querySelectorAll() {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
const workerHost = {
|
|
8
|
+
document: workerDocument,
|
|
9
|
+
postMessage(message) {
|
|
10
|
+
postMessage(message);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const popcornBrowserGlobal = ['globalThis', 'window'].join('.');
|
|
14
|
+
const popcornParentGlobal = [popcornBrowserGlobal, 'parent'].join('.');
|
|
15
|
+
const workerHostGlobal = 'globalThis.__wasmIdleElixirWorkerHost';
|
|
16
|
+
self.document = workerDocument;
|
|
17
|
+
globalThis.__wasmIdleElixirWorkerHost = workerHost;
|
|
18
|
+
class TrackedValue {
|
|
19
|
+
key;
|
|
20
|
+
value;
|
|
21
|
+
constructor({ key, value }) {
|
|
22
|
+
if (typeof key !== 'number') {
|
|
23
|
+
throw new Error('key property in TrackedValue must be a number');
|
|
24
|
+
}
|
|
25
|
+
this.key = key;
|
|
26
|
+
this.value = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
globalThis.TrackedValue = TrackedValue;
|
|
30
|
+
let bundleUrl = '';
|
|
31
|
+
let loadedBundleUrl = '';
|
|
32
|
+
let runtimePromise = null;
|
|
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
|
+
}
|
|
158
|
+
function deserialize(module, message) {
|
|
159
|
+
return JSON.parse(message, (_key, value) => {
|
|
160
|
+
const isRef = typeof value === 'object' &&
|
|
161
|
+
value !== null &&
|
|
162
|
+
Object.hasOwn(value, 'popcorn_ref') &&
|
|
163
|
+
Object.getOwnPropertyNames(value).length === 1;
|
|
164
|
+
if (!isRef) {
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
return module.trackedObjectsMap.get(value.popcorn_ref);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function ensureFunctionEval(maybeFunction) {
|
|
171
|
+
if (typeof maybeFunction !== 'function') {
|
|
172
|
+
throw new Error('Script passed to onRunTrackedJs() is not wrapped in a function');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function ensureResultKeyList(result) {
|
|
176
|
+
if (!Array.isArray(result) && result !== undefined) {
|
|
177
|
+
throw new Error('Script passed to onRunTrackedJs() returned invalid value, accepted values are arrays and undefined');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function configureTrackedObjectBridge(module, resolveReadyProcess) {
|
|
181
|
+
let appReadyProcess;
|
|
182
|
+
let elixirReady = false;
|
|
183
|
+
module.serialize = JSON.stringify;
|
|
184
|
+
module.deserialize = (message) => deserialize(module, message);
|
|
185
|
+
module.cleanupFunctions = new Map();
|
|
186
|
+
module.sendEvent = (eventName, payload) => {
|
|
187
|
+
if (eventName === 'popcorn_app_ready') {
|
|
188
|
+
const readyPayload = typeof payload === 'object' && payload !== null && Object.hasOwn(payload, 'name')
|
|
189
|
+
? payload.name
|
|
190
|
+
: payload;
|
|
191
|
+
appReadyProcess =
|
|
192
|
+
readyPayload === null || readyPayload === undefined ? null : String(readyPayload);
|
|
193
|
+
if (elixirReady) {
|
|
194
|
+
resolveReadyProcess(appReadyProcess);
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (eventName === 'popcorn_elixir_ready') {
|
|
199
|
+
elixirReady = true;
|
|
200
|
+
if (appReadyProcess !== undefined) {
|
|
201
|
+
resolveReadyProcess(appReadyProcess);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
module.onElixirReady = (process) => {
|
|
206
|
+
module.onElixirReady = null;
|
|
207
|
+
resolveReadyProcess(process);
|
|
208
|
+
};
|
|
209
|
+
module.onTrackedObjectDelete = (key) => {
|
|
210
|
+
const cleanup = module.cleanupFunctions.get(key);
|
|
211
|
+
module.cleanupFunctions.delete(key);
|
|
212
|
+
try {
|
|
213
|
+
cleanup?.();
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(error);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
module.trackedObjectsMap.delete(key);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
module.onRunTrackedJs = (scriptString, isDebug) => {
|
|
223
|
+
const trackValue = (tracked) => {
|
|
224
|
+
if (tracked instanceof TrackedValue) {
|
|
225
|
+
module.trackedObjectsMap.set(tracked.key, tracked.value);
|
|
226
|
+
return tracked.key;
|
|
227
|
+
}
|
|
228
|
+
const key = module.nextTrackedObjectKey();
|
|
229
|
+
module.trackedObjectsMap.set(key, tracked);
|
|
230
|
+
return key;
|
|
231
|
+
};
|
|
232
|
+
let fn;
|
|
233
|
+
try {
|
|
234
|
+
const indirectEval = globalThis.eval;
|
|
235
|
+
const workerScript = scriptString
|
|
236
|
+
.replaceAll(`${popcornParentGlobal}.document`, `${workerHostGlobal}.document`)
|
|
237
|
+
.replaceAll(popcornParentGlobal, workerHostGlobal)
|
|
238
|
+
.replaceAll(popcornBrowserGlobal, workerHostGlobal);
|
|
239
|
+
fn = indirectEval(workerScript);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error(error);
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
if (isDebug) {
|
|
246
|
+
ensureFunctionEval(fn);
|
|
247
|
+
}
|
|
248
|
+
let result;
|
|
249
|
+
try {
|
|
250
|
+
result = fn?.(module);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
console.error(error);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
if (isDebug) {
|
|
257
|
+
ensureResultKeyList(result);
|
|
258
|
+
}
|
|
259
|
+
return result?.map(trackValue) ?? [];
|
|
260
|
+
};
|
|
261
|
+
module.onGetTrackedObjects = (keys) => keys.map((key) => module.serialize(module.trackedObjectsMap.get(key)));
|
|
262
|
+
}
|
|
263
|
+
function configureMessagingBridge(module) {
|
|
264
|
+
const originalCast = module.cast.bind(module);
|
|
265
|
+
const originalCall = module.call.bind(module);
|
|
266
|
+
module.cast = (process, args) => {
|
|
267
|
+
originalCast(process, module.serialize(args));
|
|
268
|
+
};
|
|
269
|
+
module.call = (process, args) => originalCall(process, module.serialize(args));
|
|
270
|
+
}
|
|
271
|
+
async function startVm(avmBundle, bundleAssetUrl, log) {
|
|
272
|
+
let resolveProcess = null;
|
|
273
|
+
const processPromise = new Promise((resolve, reject) => {
|
|
274
|
+
const timeout = setTimeout(() => {
|
|
275
|
+
resolveProcess = null;
|
|
276
|
+
reject(new Error('Elixir runtime did not become ready'));
|
|
277
|
+
}, 30_000);
|
|
278
|
+
resolveProcess = (process) => {
|
|
279
|
+
clearTimeout(timeout);
|
|
280
|
+
resolveProcess = null;
|
|
281
|
+
resolve(process);
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
const atomVmModuleUrl = resolveElixirRuntimeAssetUrl('AtomVM.mjs', bundleAssetUrl);
|
|
285
|
+
const atomVmWasmUrl = resolveElixirRuntimeAssetUrl('AtomVM.wasm', bundleAssetUrl);
|
|
286
|
+
const initAtomVm = await resolveAtomVmInitializer(atomVmModuleUrl);
|
|
287
|
+
const module = (await initAtomVm({
|
|
288
|
+
locateFile(path) {
|
|
289
|
+
if (path === 'AtomVM.wasm') {
|
|
290
|
+
return atomVmWasmUrl;
|
|
291
|
+
}
|
|
292
|
+
return path;
|
|
293
|
+
},
|
|
294
|
+
preRun: [
|
|
295
|
+
({ FS }) => {
|
|
296
|
+
FS.mkdir('/data');
|
|
297
|
+
FS.writeFile('/data/bundle.avm', avmBundle);
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
arguments: ['/data/bundle.avm'],
|
|
301
|
+
print(text) {
|
|
302
|
+
postMessage({ output: text });
|
|
303
|
+
},
|
|
304
|
+
printErr(text) {
|
|
305
|
+
postMessage({ output: text });
|
|
306
|
+
},
|
|
307
|
+
onAbort() {
|
|
308
|
+
if (log) {
|
|
309
|
+
console.error('[wasm-idle:elixir-worker] AtomVM aborted');
|
|
310
|
+
}
|
|
311
|
+
setTimeout(() => postMessage({ error: 'Elixir runtime aborted' }), 100);
|
|
312
|
+
}
|
|
313
|
+
}));
|
|
314
|
+
configureTrackedObjectBridge(module, (process) => resolveProcess?.(process));
|
|
315
|
+
configureMessagingBridge(module);
|
|
316
|
+
return {
|
|
317
|
+
module,
|
|
318
|
+
process: await processPromise
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function loadRuntime(nextBundleUrl, log) {
|
|
322
|
+
if (!nextBundleUrl) {
|
|
323
|
+
throw new Error('Elixir runtime is not configured. Set PUBLIC_WASM_ELIXIR_BUNDLE_URL or runtimeAssets.elixir.bundleUrl.');
|
|
324
|
+
}
|
|
325
|
+
if (loadedBundleUrl === nextBundleUrl && runtimePromise) {
|
|
326
|
+
return await runtimePromise;
|
|
327
|
+
}
|
|
328
|
+
loadedBundleUrl = nextBundleUrl;
|
|
329
|
+
runtimePromise = (async () => {
|
|
330
|
+
if (log) {
|
|
331
|
+
console.log(`[wasm-idle:elixir-worker] load bundleUrl=${nextBundleUrl}`);
|
|
332
|
+
}
|
|
333
|
+
const bundleBuffer = await fetch(nextBundleUrl).then((response) => {
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
throw new Error(`Failed to fetch Elixir bundle: ${response.status} ${response.statusText}`);
|
|
336
|
+
}
|
|
337
|
+
return response.arrayBuffer();
|
|
338
|
+
});
|
|
339
|
+
return await startVm(new Int8Array(bundleBuffer), nextBundleUrl, log);
|
|
340
|
+
})();
|
|
341
|
+
return await runtimePromise;
|
|
342
|
+
}
|
|
343
|
+
function normalizeCallError(module, error) {
|
|
344
|
+
if (error === 'noproc') {
|
|
345
|
+
runtimePromise = null;
|
|
346
|
+
loadedBundleUrl = '';
|
|
347
|
+
return 'Elixir runtime process is unavailable';
|
|
348
|
+
}
|
|
349
|
+
if (typeof error === 'string') {
|
|
350
|
+
try {
|
|
351
|
+
const value = module.deserialize(error);
|
|
352
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (error instanceof Error) {
|
|
359
|
+
return error.message;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
return JSON.stringify(error);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return String(error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
self.onmessage = async (event) => {
|
|
369
|
+
const { load, bundleUrl: nextBundleUrl, buffer, code, prepare, log = true } = event.data;
|
|
370
|
+
try {
|
|
371
|
+
if (load) {
|
|
372
|
+
bundleUrl = nextBundleUrl;
|
|
373
|
+
await loadRuntime(bundleUrl, log);
|
|
374
|
+
postMessage({ load: true });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (!bundleUrl) {
|
|
378
|
+
throw new Error('Elixir runtime not loaded');
|
|
379
|
+
}
|
|
380
|
+
stdinBufferElixir = buffer ? new Int32Array(buffer) : null;
|
|
381
|
+
if (prepare) {
|
|
382
|
+
postMessage({ results: true });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const runtime = await loadRuntime(bundleUrl, log);
|
|
386
|
+
if (!runtime.process) {
|
|
387
|
+
throw new Error('Elixir runtime did not expose a default process');
|
|
388
|
+
}
|
|
389
|
+
if (log) {
|
|
390
|
+
console.log(`[wasm-idle:elixir-worker] eval bytes=${code.length}`);
|
|
391
|
+
}
|
|
392
|
+
let evalCode = code;
|
|
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;
|
|
560
|
+
}
|
|
561
|
+
const response = await runtime.module.call(runtime.process, ['eval_elixir', evalCode]);
|
|
562
|
+
const rendered = runtime.module.deserialize(response);
|
|
563
|
+
if (typeof rendered === 'string') {
|
|
564
|
+
postMessage({ results: rendered });
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (rendered === undefined) {
|
|
568
|
+
postMessage({ results: true });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
postMessage({ results: JSON.stringify(rendered) });
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
if (log) {
|
|
575
|
+
console.error('[wasm-idle:elixir-worker] failed', error);
|
|
576
|
+
}
|
|
577
|
+
if (runtimePromise) {
|
|
578
|
+
try {
|
|
579
|
+
const runtime = await runtimePromise;
|
|
580
|
+
postMessage({ error: normalizeCallError(runtime.module, error) });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
// Ignore secondary runtime load errors and surface the original failure instead.
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
postMessage({
|
|
588
|
+
error: error instanceof Error ? error.message : String(error)
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|