@sanbus/galley-wasm 0.0.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 +8 -0
- package/build.mjs +37 -0
- package/dist/dispatch.d.ts +12 -0
- package/dist/dispatch.js +62 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/ffi.d.ts +303 -0
- package/dist/ffi.js +1115 -0
- package/dist/ffi.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.js +23 -0
- package/dist/session.js.map +1 -0
- package/package.json +48 -0
- package/src/dispatch.ts +65 -0
- package/src/ffi.ts +1526 -0
- package/src/index.ts +82 -0
- package/src/session.ts +26 -0
package/dist/ffi.js
ADDED
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAssembly adapter for the Galley JavaScript bindings: a `WasmPort`
|
|
3
|
+
* implementing the core `FfiPort` over a WASI reactor module built from
|
|
4
|
+
* `bindings/c/galley.h` (`zig build -Dwasm`, see `build.mjs`).
|
|
5
|
+
*
|
|
6
|
+
* Zero npm dependencies. The module is instantiated with a minimal
|
|
7
|
+
* in-TS `wasi_snapshot_preview1` stub (real `random_get`/`clock_time_get`,
|
|
8
|
+
* filesystem calls report unavailable — the parse path never touches the
|
|
9
|
+
* filesystem) plus an `env.galley_js_dispatch_id` import that forwards
|
|
10
|
+
* procedure-hook IDs to the core registry. All memory copying and integer
|
|
11
|
+
* normalization live here; all session logic lives in `@sanbus/galley-core`.
|
|
12
|
+
*
|
|
13
|
+
* Initialization is async (`await init()`), except under Node where the
|
|
14
|
+
* file can be read and instantiated synchronously — `Session` and the
|
|
15
|
+
* module-level queries auto-initialize there, so Node demos stay
|
|
16
|
+
* synchronous. Elsewhere (browsers) the gate throws `NeedInitError` until
|
|
17
|
+
* `await init()` completes. Views into wasm memory are never cached:
|
|
18
|
+
* `malloc` may grow memory and detach old views.
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from "node:fs";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import process from "node:process";
|
|
23
|
+
import { GalleyError, dispatchProcedure, resolveArtifact, wasmArtifactFileName } from "@sanbus/galley-core";
|
|
24
|
+
const LIBRARY_BASE = "galley-js-wasm";
|
|
25
|
+
const WASI_NOSYS = 52;
|
|
26
|
+
const WASI_BADF = 8;
|
|
27
|
+
export class NeedInitError extends Error {
|
|
28
|
+
constructor(libraryPath) {
|
|
29
|
+
super(`galley-wasm: WebAssembly module${libraryPath ? ` for ${libraryPath}` : ""} is not initialized. ` +
|
|
30
|
+
`Call "await init()" (or "await init({ url })" / "init({ bytes })" in browsers) first.`);
|
|
31
|
+
this.name = "NeedInitError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// --- instance cache (one module per grammar file) --------------------------
|
|
35
|
+
const ports = new Map();
|
|
36
|
+
let seededDefault = null;
|
|
37
|
+
function isNode() {
|
|
38
|
+
return (typeof process !== "undefined" &&
|
|
39
|
+
typeof process.versions?.node === "string");
|
|
40
|
+
}
|
|
41
|
+
// --- library discovery (mirrors the Node adapter, `.wasm` names) -----------
|
|
42
|
+
// One place, named up front: an explicit path or GALLEY_LIBRARY_PATH.
|
|
43
|
+
// Anything else is a loud error, never a search.
|
|
44
|
+
const BUILD_HINT = `Build it first: npx galley-js-wasm <language-dir>\n` +
|
|
45
|
+
`or set GALLEY_LIBRARY_PATH=/path/to/${wasmFileName()}`;
|
|
46
|
+
export function wasmFileName(base = LIBRARY_BASE) {
|
|
47
|
+
return wasmArtifactFileName(base);
|
|
48
|
+
}
|
|
49
|
+
function exists(localPath) {
|
|
50
|
+
try {
|
|
51
|
+
fs.accessSync(localPath);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function findLibrary(explicit) {
|
|
59
|
+
return resolveArtifact(explicit, {
|
|
60
|
+
getEnv: (name) => process.env[name],
|
|
61
|
+
resolvePath: (candidate) => path.resolve(candidate),
|
|
62
|
+
existsSync: exists,
|
|
63
|
+
buildHint: BUILD_HINT,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// --- minimal WASI stub ------------------------------------------------------
|
|
67
|
+
// Real entropy and clocks; filesystem calls report unavailable. The parse
|
|
68
|
+
// path never touches the filesystem (`parseFile` is served by the host
|
|
69
|
+
// reading the file into a buffer first).
|
|
70
|
+
function makeWasiStub(getMemory) {
|
|
71
|
+
const view = () => new DataView(getMemory());
|
|
72
|
+
const bytes = () => new Uint8Array(getMemory());
|
|
73
|
+
const fail = () => WASI_NOSYS;
|
|
74
|
+
return {
|
|
75
|
+
random_get: (ptr, len) => {
|
|
76
|
+
crypto.getRandomValues(bytes().subarray(ptr, ptr + len));
|
|
77
|
+
return 0;
|
|
78
|
+
},
|
|
79
|
+
clock_res_get: (_id, resPtr) => {
|
|
80
|
+
view().setBigUint64(resPtr, 1n, true);
|
|
81
|
+
return 0;
|
|
82
|
+
},
|
|
83
|
+
clock_time_get: (_id, _precision, timePtr) => {
|
|
84
|
+
view().setBigUint64(timePtr, BigInt(Date.now()) * 1000000n, true);
|
|
85
|
+
return 0;
|
|
86
|
+
},
|
|
87
|
+
fd_write: (fd, iovs, iovsLen, nwrittenPtr) => {
|
|
88
|
+
try {
|
|
89
|
+
const dataView = view();
|
|
90
|
+
let written = 0;
|
|
91
|
+
const chunks = [];
|
|
92
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
93
|
+
const base = dataView.getUint32(iovs + i * 8, true);
|
|
94
|
+
const len = dataView.getUint32(iovs + i * 8 + 4, true);
|
|
95
|
+
chunks.push(bytes().slice(base, base + len));
|
|
96
|
+
written += len;
|
|
97
|
+
}
|
|
98
|
+
if (fd === 1 || fd === 2) {
|
|
99
|
+
const text = chunks.map((c) => new TextDecoder().decode(c)).join("");
|
|
100
|
+
if (isNode()) {
|
|
101
|
+
(fd === 1 ? process.stdout : process.stderr).write(text);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(text);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
dataView.setUint32(nwrittenPtr, written, true);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return WASI_NOSYS;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
proc_exit: (code) => {
|
|
115
|
+
throw new Error(`galley-wasm: guest called proc_exit(${code})`);
|
|
116
|
+
},
|
|
117
|
+
// No preopened directories: BADF ends the preopen scan (NOSYS aborts libc init).
|
|
118
|
+
fd_prestat_get: () => WASI_BADF,
|
|
119
|
+
fd_fdstat_get: fail,
|
|
120
|
+
fd_filestat_get: fail,
|
|
121
|
+
fd_filestat_set_size: fail,
|
|
122
|
+
fd_filestat_set_times: fail,
|
|
123
|
+
fd_pread: fail,
|
|
124
|
+
fd_prestat_dir_name: fail,
|
|
125
|
+
fd_pwrite: fail,
|
|
126
|
+
fd_read: fail,
|
|
127
|
+
fd_seek: fail,
|
|
128
|
+
path_create_directory: fail,
|
|
129
|
+
path_filestat_get: fail,
|
|
130
|
+
path_filestat_set_times: fail,
|
|
131
|
+
path_link: fail,
|
|
132
|
+
path_open: fail,
|
|
133
|
+
path_readlink: fail,
|
|
134
|
+
path_remove_directory: fail,
|
|
135
|
+
path_rename: fail,
|
|
136
|
+
path_symlink: fail,
|
|
137
|
+
path_unlink_file: fail,
|
|
138
|
+
poll_oneoff: fail,
|
|
139
|
+
fd_sync: fail,
|
|
140
|
+
fd_readdir: fail,
|
|
141
|
+
fd_close: fail,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function makeImports(pending) {
|
|
145
|
+
return {
|
|
146
|
+
wasi_snapshot_preview1: makeWasiStub(() => {
|
|
147
|
+
if (pending.memory === null)
|
|
148
|
+
throw new Error("galley-wasm: memory unavailable");
|
|
149
|
+
return pending.memory;
|
|
150
|
+
}),
|
|
151
|
+
env: {
|
|
152
|
+
// Current builds import the ID entry; older modules import the
|
|
153
|
+
// name-carrying one. Both are always provided so either links.
|
|
154
|
+
galley_js_dispatch_id: (id, argsPtr) => {
|
|
155
|
+
const port = pending.port;
|
|
156
|
+
if (port === null)
|
|
157
|
+
return;
|
|
158
|
+
port.dispatchFromGuestById(id, argsPtr);
|
|
159
|
+
},
|
|
160
|
+
galley_js_dispatch: (namePtr, nameLen, argsPtr) => {
|
|
161
|
+
const port = pending.port;
|
|
162
|
+
if (port === null)
|
|
163
|
+
return;
|
|
164
|
+
port.dispatchFromGuest(namePtr, nameLen, argsPtr);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function adoptInstance(instance, wasmPath, pending) {
|
|
170
|
+
pending.memory = instance.exports.memory.buffer;
|
|
171
|
+
if (typeof instance.exports._initialize === "function") {
|
|
172
|
+
instance.exports._initialize();
|
|
173
|
+
}
|
|
174
|
+
const port = new WasmPort(instance.exports, wasmPath);
|
|
175
|
+
pending.port = port;
|
|
176
|
+
ports.set(wasmPath, port);
|
|
177
|
+
return port;
|
|
178
|
+
}
|
|
179
|
+
function instantiate(bytes, wasmPath) {
|
|
180
|
+
const pending = { port: null, memory: null };
|
|
181
|
+
const instance = new WebAssembly.Instance(new WebAssembly.Module(bytes), makeImports(pending));
|
|
182
|
+
return adoptInstance(instance, wasmPath, pending);
|
|
183
|
+
}
|
|
184
|
+
function loadBytesSync(options) {
|
|
185
|
+
if (options.bytes) {
|
|
186
|
+
return {
|
|
187
|
+
bytes: Uint8Array.from(options.bytes),
|
|
188
|
+
wasmPath: options.libraryPath ?? seededDefault ?? "<bytes>",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (options.url !== undefined) {
|
|
192
|
+
throw new NeedInitError(options.libraryPath);
|
|
193
|
+
}
|
|
194
|
+
if (!isNode())
|
|
195
|
+
throw new NeedInitError(options.libraryPath);
|
|
196
|
+
// findLibrary throws MissingArtifactError naming the exact place.
|
|
197
|
+
const wasmPath = options.libraryPath ?? seededDefault ?? findLibrary();
|
|
198
|
+
return { bytes: Uint8Array.from(new Uint8Array(fs.readFileSync(wasmPath))), wasmPath };
|
|
199
|
+
}
|
|
200
|
+
/** Async entry point; the only way to initialize in browsers. */
|
|
201
|
+
export async function init(options = {}) {
|
|
202
|
+
if (options.bytes) {
|
|
203
|
+
const wasmPath = options.libraryPath ?? seededDefault ?? "<bytes>";
|
|
204
|
+
instantiate(Uint8Array.from(options.bytes), wasmPath);
|
|
205
|
+
if (options.libraryPath === undefined)
|
|
206
|
+
seededDefault = wasmPath;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (options.url !== undefined) {
|
|
210
|
+
const response = await fetch(options.url);
|
|
211
|
+
if (!response.ok)
|
|
212
|
+
throw new Error(`galley-wasm: failed to fetch ${options.url}: ${response.status}`);
|
|
213
|
+
const wasmPath = options.libraryPath ?? seededDefault ?? String(options.url);
|
|
214
|
+
instantiate(new Uint8Array(await response.arrayBuffer()), wasmPath);
|
|
215
|
+
if (options.libraryPath === undefined)
|
|
216
|
+
seededDefault = wasmPath;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!isNode())
|
|
220
|
+
throw new NeedInitError(options.libraryPath);
|
|
221
|
+
const { bytes, wasmPath } = loadBytesSync(options);
|
|
222
|
+
// Asynchronous compile for streaming-friendly startup; semantics match initSync.
|
|
223
|
+
const pending = { port: null, memory: null };
|
|
224
|
+
const instance = await WebAssembly.instantiate(await WebAssembly.compile(bytes), makeImports(pending));
|
|
225
|
+
adoptInstance(instance, wasmPath, pending);
|
|
226
|
+
}
|
|
227
|
+
/** Synchronous entry point; Node only (file read + `WebAssembly.Module`). */
|
|
228
|
+
export function initSync(options = {}) {
|
|
229
|
+
const { bytes, wasmPath } = loadBytesSync(options);
|
|
230
|
+
instantiate(bytes, wasmPath);
|
|
231
|
+
if (options.libraryPath === undefined)
|
|
232
|
+
seededDefault = wasmPath;
|
|
233
|
+
}
|
|
234
|
+
/** Seed the default cache key (used by `init({ bytes })` without a path). */
|
|
235
|
+
export function seedDefault(wasmPath) {
|
|
236
|
+
seededDefault = wasmPath;
|
|
237
|
+
}
|
|
238
|
+
function resolveKey(explicit) {
|
|
239
|
+
if (explicit)
|
|
240
|
+
return explicit;
|
|
241
|
+
if (seededDefault !== null)
|
|
242
|
+
return seededDefault;
|
|
243
|
+
if (!isNode())
|
|
244
|
+
throw new NeedInitError();
|
|
245
|
+
return findLibrary();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Single gate for every consumer: returns the initialized port for a
|
|
249
|
+
* grammar, auto-initializing synchronously under Node. Throws
|
|
250
|
+
* `NeedInitError` anywhere synchronous initialization is impossible.
|
|
251
|
+
*/
|
|
252
|
+
export function getWasmPort(libraryPath) {
|
|
253
|
+
const key = libraryPath ?? resolveKey();
|
|
254
|
+
const cached = ports.get(key);
|
|
255
|
+
if (cached)
|
|
256
|
+
return cached;
|
|
257
|
+
if (!isNode())
|
|
258
|
+
throw new NeedInitError(libraryPath);
|
|
259
|
+
const resolved = libraryPath ?? findLibrary();
|
|
260
|
+
const direct = ports.get(resolved);
|
|
261
|
+
if (direct)
|
|
262
|
+
return direct;
|
|
263
|
+
initSync(libraryPath ? { libraryPath: resolved } : {});
|
|
264
|
+
const port = ports.get(resolved) ?? ports.get(key);
|
|
265
|
+
if (!port)
|
|
266
|
+
throw new NeedInitError(libraryPath);
|
|
267
|
+
return port;
|
|
268
|
+
}
|
|
269
|
+
// --- helpers ----------------------------------------------------------------
|
|
270
|
+
function isNegative(status) {
|
|
271
|
+
return status < 0n;
|
|
272
|
+
}
|
|
273
|
+
function toNumber(value) {
|
|
274
|
+
return Number(value);
|
|
275
|
+
}
|
|
276
|
+
/** Reinterpret a guest i64 as an unsigned u64 address (INVALID_NODE survives). */
|
|
277
|
+
function asAddress(value) {
|
|
278
|
+
return BigInt.asUintN(64, value);
|
|
279
|
+
}
|
|
280
|
+
/** Encode a u64 address (possibly INVALID_NODE) as a guest i64. */
|
|
281
|
+
function asI64(value) {
|
|
282
|
+
return BigInt.asIntN(64, value);
|
|
283
|
+
}
|
|
284
|
+
const textEncoder = new TextEncoder();
|
|
285
|
+
const textDecoder = new TextDecoder();
|
|
286
|
+
/**
|
|
287
|
+
* The wasm `FfiPort`: normalizes the reactor module's i32/i64 boundary
|
|
288
|
+
* into the structured values the core expects. Memory is allocated with
|
|
289
|
+
* the guest's `galley_js_malloc`/`galley_js_free`; every view is fresh
|
|
290
|
+
* because allocation may grow (and detach) memory.
|
|
291
|
+
*/
|
|
292
|
+
export class WasmPort {
|
|
293
|
+
wasm;
|
|
294
|
+
libraryPath;
|
|
295
|
+
constructor(wasm, libraryPath) {
|
|
296
|
+
this.wasm = wasm;
|
|
297
|
+
this.libraryPath = libraryPath;
|
|
298
|
+
}
|
|
299
|
+
/** Guest hook entry: decode the name and forward to the core registry. */
|
|
300
|
+
dispatchFromGuest(namePtr, nameLen, argsPtr) {
|
|
301
|
+
let name;
|
|
302
|
+
try {
|
|
303
|
+
name = textDecoder.decode(this.readBytes(namePtr, nameLen));
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error("galley procedure dispatch: failed to decode name", error);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
dispatchProcedure(name, argsPtr, this);
|
|
310
|
+
}
|
|
311
|
+
/** Guest hook entry (current builds): integer hook ID, no strings cross. */
|
|
312
|
+
dispatchFromGuestById(id, argsPtr) {
|
|
313
|
+
const name = this.procedureNames()[id];
|
|
314
|
+
if (name === undefined)
|
|
315
|
+
return;
|
|
316
|
+
dispatchProcedure(name, argsPtr, this);
|
|
317
|
+
}
|
|
318
|
+
#procedureNameTable = null;
|
|
319
|
+
procedureNames() {
|
|
320
|
+
if (this.#procedureNameTable !== null)
|
|
321
|
+
return this.#procedureNameTable;
|
|
322
|
+
const table = [];
|
|
323
|
+
if (typeof this.wasm.galley_js_procedure_count === "function" &&
|
|
324
|
+
typeof this.wasm.galley_js_procedure_name_ptr === "function" &&
|
|
325
|
+
typeof this.wasm.galley_js_procedure_name_len === "function") {
|
|
326
|
+
const n = this.wasm.galley_js_procedure_count();
|
|
327
|
+
for (let i = 0; i < n; i++) {
|
|
328
|
+
const ptrValue = this.wasm.galley_js_procedure_name_ptr(i);
|
|
329
|
+
if (ptrValue === 0)
|
|
330
|
+
break;
|
|
331
|
+
table.push(textDecoder.decode(this.readBytes(ptrValue, this.wasm.galley_js_procedure_name_len(i))));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
this.#procedureNameTable = table;
|
|
335
|
+
return table;
|
|
336
|
+
}
|
|
337
|
+
// -- memory -------------------------------------------------------------
|
|
338
|
+
memoryBytes() {
|
|
339
|
+
return new Uint8Array(this.wasm.memory.buffer);
|
|
340
|
+
}
|
|
341
|
+
dataView() {
|
|
342
|
+
return new DataView(this.wasm.memory.buffer);
|
|
343
|
+
}
|
|
344
|
+
malloc(len) {
|
|
345
|
+
const ptr = this.wasm.galley_js_malloc(len);
|
|
346
|
+
if (ptr === 0)
|
|
347
|
+
throw new Error("galley-wasm: out of memory");
|
|
348
|
+
return ptr;
|
|
349
|
+
}
|
|
350
|
+
free(ptr, len) {
|
|
351
|
+
if (len === 0)
|
|
352
|
+
return;
|
|
353
|
+
this.wasm.galley_js_free(ptr, len);
|
|
354
|
+
}
|
|
355
|
+
/** Copy guest bytes out (owned copy, valid after the next call). */
|
|
356
|
+
readBytes(ptr, len) {
|
|
357
|
+
if (ptr === 0 || len === 0)
|
|
358
|
+
return new Uint8Array(0);
|
|
359
|
+
return this.memoryBytes().slice(ptr, ptr + len);
|
|
360
|
+
}
|
|
361
|
+
readCString(ptr) {
|
|
362
|
+
if (ptr === 0)
|
|
363
|
+
return "";
|
|
364
|
+
const memory = this.memoryBytes();
|
|
365
|
+
let end = ptr;
|
|
366
|
+
while (memory[end] !== 0)
|
|
367
|
+
end++;
|
|
368
|
+
return textDecoder.decode(memory.subarray(ptr, end));
|
|
369
|
+
}
|
|
370
|
+
/** Copy host bytes in; zero-length inputs still get a non-null slot. */
|
|
371
|
+
writeBytes(data) {
|
|
372
|
+
const len = data.length;
|
|
373
|
+
const ptr = this.malloc(Math.max(len, 1));
|
|
374
|
+
if (len > 0)
|
|
375
|
+
this.memoryBytes().set(data, ptr);
|
|
376
|
+
return { ptr, len };
|
|
377
|
+
}
|
|
378
|
+
// -- module-level queries -----------------------------------------------
|
|
379
|
+
version() {
|
|
380
|
+
return this.readCString(this.wasm.galley_version());
|
|
381
|
+
}
|
|
382
|
+
parserType() {
|
|
383
|
+
return toNumber(this.wasm.galley_parser_type());
|
|
384
|
+
}
|
|
385
|
+
errorRecoveryMode() {
|
|
386
|
+
return toNumber(this.wasm.galley_error_recovery_mode());
|
|
387
|
+
}
|
|
388
|
+
hasAst() {
|
|
389
|
+
return this.wasm.galley_has_ast() !== 0;
|
|
390
|
+
}
|
|
391
|
+
hasProcedures() {
|
|
392
|
+
return this.wasm.galley_has_procedures() !== 0;
|
|
393
|
+
}
|
|
394
|
+
allowsNoAstTreeProcedures() {
|
|
395
|
+
return this.wasm.galley_allows_no_ast_tree_procedures() !== 0;
|
|
396
|
+
}
|
|
397
|
+
sourceRetentionEnabled() {
|
|
398
|
+
return this.wasm.galley_source_retention_enabled() !== 0;
|
|
399
|
+
}
|
|
400
|
+
hasPositionTracking() {
|
|
401
|
+
return this.wasm.galley_has_position_tracking() !== 0;
|
|
402
|
+
}
|
|
403
|
+
hasInputStreaming() {
|
|
404
|
+
return this.wasm.galley_has_input_streaming() !== 0;
|
|
405
|
+
}
|
|
406
|
+
usesVerbatim() {
|
|
407
|
+
return this.wasm.galley_uses_verbatim() !== 0;
|
|
408
|
+
}
|
|
409
|
+
stackOverflowRecoveryAvailable() {
|
|
410
|
+
return this.wasm.galley_stack_overflow_recovery_available() !== 0;
|
|
411
|
+
}
|
|
412
|
+
symbolCount() {
|
|
413
|
+
return toNumber(this.wasm.galley_symbol_count());
|
|
414
|
+
}
|
|
415
|
+
variableCount() {
|
|
416
|
+
return toNumber(this.wasm.galley_variable_count());
|
|
417
|
+
}
|
|
418
|
+
statusString(status) {
|
|
419
|
+
const ptr = this.wasm.galley_status_string(BigInt(status));
|
|
420
|
+
if (ptr === 0)
|
|
421
|
+
return null;
|
|
422
|
+
return this.readCString(ptr);
|
|
423
|
+
}
|
|
424
|
+
// -- sessions ------------------------------------------------------------
|
|
425
|
+
createSession(options) {
|
|
426
|
+
if (options === null) {
|
|
427
|
+
const handle = this.wasm.galley_session_create();
|
|
428
|
+
if (handle === 0)
|
|
429
|
+
return null;
|
|
430
|
+
return handle;
|
|
431
|
+
}
|
|
432
|
+
// GalleyCOptions layout (wasm32, little-endian): 5x i32/u32, pad, f64, u64.
|
|
433
|
+
const ptr = this.malloc(40);
|
|
434
|
+
try {
|
|
435
|
+
const view = this.dataView();
|
|
436
|
+
view.setInt32(ptr, options.maxErrors, true);
|
|
437
|
+
view.setInt32(ptr + 4, options.recoveryWindow, true);
|
|
438
|
+
view.setInt32(ptr + 8, options.stackOverflowRecovery, true);
|
|
439
|
+
view.setUint32(ptr + 12, options.syntaxErrorStackDepth, true);
|
|
440
|
+
view.setInt32(ptr + 16, options.verbosity, true);
|
|
441
|
+
view.setFloat64(ptr + 24, options.astPreallocationRatio, true);
|
|
442
|
+
view.setBigUint64(ptr + 32, options.astPreallocationCap, true);
|
|
443
|
+
const handle = this.wasm.galley_session_create_ex(ptr);
|
|
444
|
+
if (handle === 0)
|
|
445
|
+
return null;
|
|
446
|
+
return handle;
|
|
447
|
+
}
|
|
448
|
+
finally {
|
|
449
|
+
this.free(ptr, 40);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
destroySession(handle) {
|
|
453
|
+
this.wasm.galley_session_destroy(handle);
|
|
454
|
+
}
|
|
455
|
+
setMessageOverride(handle, name, message) {
|
|
456
|
+
const nameBytes = textEncoder.encode(textDecoder.decode(name));
|
|
457
|
+
const messageBytes = textEncoder.encode(textDecoder.decode(message));
|
|
458
|
+
const nameSlot = this.writeBytes(nameBytes);
|
|
459
|
+
const messageSlot = this.writeBytes(messageBytes);
|
|
460
|
+
try {
|
|
461
|
+
return toNumber(this.wasm.galley_session_set_message_override(handle, nameSlot.ptr, nameSlot.len, messageSlot.ptr, messageSlot.len));
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
this.free(nameSlot.ptr, Math.max(nameSlot.len, 1));
|
|
465
|
+
this.free(messageSlot.ptr, Math.max(messageSlot.len, 1));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// -- parsing --------------------------------------------------------------
|
|
469
|
+
parse(handle, data) {
|
|
470
|
+
const slot = this.writeBytes(data);
|
|
471
|
+
try {
|
|
472
|
+
return toNumber(this.wasm.galley_parse(handle, slot.ptr, slot.len));
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
this.free(slot.ptr, Math.max(slot.len, 1));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
parseFile(handle, filePath) {
|
|
479
|
+
// No guest filesystem: the host reads the file, then parses bytes.
|
|
480
|
+
// Mirrors the native `galley_error_io` (-11) contract on read failure.
|
|
481
|
+
let data;
|
|
482
|
+
try {
|
|
483
|
+
data = new Uint8Array(fs.readFileSync(filePath));
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
return -11;
|
|
487
|
+
}
|
|
488
|
+
return this.parse(handle, data);
|
|
489
|
+
}
|
|
490
|
+
lastPosition(handle) {
|
|
491
|
+
const out = this.malloc(8);
|
|
492
|
+
try {
|
|
493
|
+
const status = this.wasm.galley_last_position(handle, out, out + 4);
|
|
494
|
+
if (isNegative(status))
|
|
495
|
+
return null;
|
|
496
|
+
const view = this.dataView();
|
|
497
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
this.free(out, 8);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// -- arena and navigation ---------------------------------------------------
|
|
504
|
+
nodeCount(handle) {
|
|
505
|
+
return toNumber(this.wasm.galley_node_count(handle));
|
|
506
|
+
}
|
|
507
|
+
reserveNodes(handle, capacity) {
|
|
508
|
+
return toNumber(this.wasm.galley_reserve_nodes(handle, asI64(capacity)));
|
|
509
|
+
}
|
|
510
|
+
nodeCapacity(handle) {
|
|
511
|
+
return toNumber(this.wasm.galley_node_capacity(handle));
|
|
512
|
+
}
|
|
513
|
+
rootNode(handle) {
|
|
514
|
+
return asAddress(this.wasm.galley_root_node(handle));
|
|
515
|
+
}
|
|
516
|
+
nodeValid(handle, node) {
|
|
517
|
+
return this.wasm.galley_node_is_valid(handle, asI64(node)) !== 0;
|
|
518
|
+
}
|
|
519
|
+
childCount(handle, node) {
|
|
520
|
+
return this.wasm.galley_node_child_count(handle, asI64(node));
|
|
521
|
+
}
|
|
522
|
+
firstChild(handle, node) {
|
|
523
|
+
return asAddress(this.wasm.galley_node_first_child(handle, asI64(node)));
|
|
524
|
+
}
|
|
525
|
+
lastChild(handle, node) {
|
|
526
|
+
return asAddress(this.wasm.galley_node_last_child(handle, asI64(node)));
|
|
527
|
+
}
|
|
528
|
+
nextSibling(handle, node) {
|
|
529
|
+
return asAddress(this.wasm.galley_node_next_sibling(handle, asI64(node)));
|
|
530
|
+
}
|
|
531
|
+
priorSibling(handle, node) {
|
|
532
|
+
return asAddress(this.wasm.galley_node_prior_sibling(handle, asI64(node)));
|
|
533
|
+
}
|
|
534
|
+
parent(handle, node) {
|
|
535
|
+
return asAddress(this.wasm.galley_node_parent(handle, asI64(node)));
|
|
536
|
+
}
|
|
537
|
+
treeSnapshot(handle) {
|
|
538
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
539
|
+
const count = this.nodeCount(handle);
|
|
540
|
+
const empty = {
|
|
541
|
+
count,
|
|
542
|
+
parent: new BigUint64Array(0),
|
|
543
|
+
firstChild: new BigUint64Array(0),
|
|
544
|
+
next: new BigUint64Array(0),
|
|
545
|
+
childCount: new Uint32Array(0),
|
|
546
|
+
variable: new BigInt64Array(0),
|
|
547
|
+
spanStart: new BigUint64Array(0),
|
|
548
|
+
spanLen: new BigUint64Array(0),
|
|
549
|
+
};
|
|
550
|
+
if (count === 0)
|
|
551
|
+
return empty;
|
|
552
|
+
// Eight-byte columns first (parent, firstChild, next, spanStart,
|
|
553
|
+
// spanLen, variable), then the u32 childCount tail: every column
|
|
554
|
+
// stays naturally aligned for bulk typed-array copies.
|
|
555
|
+
const stride = count * 8;
|
|
556
|
+
const offParent = 0;
|
|
557
|
+
const offFirst = stride;
|
|
558
|
+
const offNext = stride * 2;
|
|
559
|
+
const offSpanStart = stride * 3;
|
|
560
|
+
const offSpanLen = stride * 4;
|
|
561
|
+
const offVariable = stride * 5;
|
|
562
|
+
const offChildCount = stride * 6;
|
|
563
|
+
const total = offChildCount + count * 4;
|
|
564
|
+
const base = this.malloc(total);
|
|
565
|
+
try {
|
|
566
|
+
const status = this.wasm.galley_tree_snapshot(handle, base + offParent, base + offFirst, base + offNext, base + offChildCount, base + offVariable, base + offSpanStart, base + offSpanLen, BigInt(count));
|
|
567
|
+
if (isNegative(status))
|
|
568
|
+
throw new GalleyError("galley_tree_snapshot failed", Number(status));
|
|
569
|
+
if (status !== BigInt(count))
|
|
570
|
+
continue;
|
|
571
|
+
const memory = this.memoryBytes();
|
|
572
|
+
const column64 = (offset) => new BigUint64Array(memory.buffer, memory.byteOffset + base + offset, count);
|
|
573
|
+
const parent = new BigUint64Array(count);
|
|
574
|
+
parent.set(column64(offParent));
|
|
575
|
+
const firstChild = new BigUint64Array(count);
|
|
576
|
+
firstChild.set(column64(offFirst));
|
|
577
|
+
const next = new BigUint64Array(count);
|
|
578
|
+
next.set(column64(offNext));
|
|
579
|
+
const spanStart = new BigUint64Array(count);
|
|
580
|
+
spanStart.set(column64(offSpanStart));
|
|
581
|
+
const spanLen = new BigUint64Array(count);
|
|
582
|
+
spanLen.set(column64(offSpanLen));
|
|
583
|
+
const variable = new BigInt64Array(count);
|
|
584
|
+
variable.set(new BigInt64Array(memory.buffer, memory.byteOffset + base + offVariable, count));
|
|
585
|
+
const childCount = new Uint32Array(count);
|
|
586
|
+
childCount.set(new Uint32Array(memory.buffer, memory.byteOffset + base + offChildCount, count));
|
|
587
|
+
return { count, parent, firstChild, next, childCount, variable, spanStart, spanLen };
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
this.free(base, total);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
throw new GalleyError("node count changed during galley_tree_snapshot", -8);
|
|
594
|
+
}
|
|
595
|
+
// -- walker -------------------------------------------------------------------
|
|
596
|
+
walkerCreate(handle, node, skipSemanticErrors) {
|
|
597
|
+
const walker = this.wasm.galley_walker_create(handle, asI64(node), skipSemanticErrors ? 1 : 0);
|
|
598
|
+
if (walker === 0)
|
|
599
|
+
return null;
|
|
600
|
+
return walker;
|
|
601
|
+
}
|
|
602
|
+
walkerNext(walker) {
|
|
603
|
+
const out = this.malloc(16);
|
|
604
|
+
try {
|
|
605
|
+
const yielded = this.wasm.galley_walker_next(walker, out, out + 8, out + 12);
|
|
606
|
+
if (yielded === 0)
|
|
607
|
+
return null;
|
|
608
|
+
const view = this.dataView();
|
|
609
|
+
return {
|
|
610
|
+
node: view.getBigUint64(out, true),
|
|
611
|
+
depth: view.getUint32(out + 8, true),
|
|
612
|
+
isSemanticError: view.getInt32(out + 12, true) !== 0,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
this.free(out, 16);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
walkerSkipChildren(walker) {
|
|
620
|
+
this.wasm.galley_walker_skip_children(walker);
|
|
621
|
+
}
|
|
622
|
+
walkerDestroy(walker) {
|
|
623
|
+
this.wasm.galley_walker_destroy(walker);
|
|
624
|
+
}
|
|
625
|
+
// -- node accessors ------------------------------------------------------------
|
|
626
|
+
/** Read a guest `(data, len)` byte pair; null on negative status. */
|
|
627
|
+
tryCopyBytes(call) {
|
|
628
|
+
const out = this.malloc(8);
|
|
629
|
+
try {
|
|
630
|
+
const status = call(out, out + 4);
|
|
631
|
+
if (isNegative(status))
|
|
632
|
+
return null;
|
|
633
|
+
const view = this.dataView();
|
|
634
|
+
const ptr = view.getUint32(out, true);
|
|
635
|
+
const len = view.getUint32(out + 4, true);
|
|
636
|
+
if (ptr === 0)
|
|
637
|
+
return null;
|
|
638
|
+
return this.readBytes(ptr, len);
|
|
639
|
+
}
|
|
640
|
+
finally {
|
|
641
|
+
this.free(out, 8);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
readSemanticPair(call) {
|
|
645
|
+
const out = this.malloc(16);
|
|
646
|
+
try {
|
|
647
|
+
const status = call(out, out + 4, out + 8, out + 12);
|
|
648
|
+
if (isNegative(status))
|
|
649
|
+
return null;
|
|
650
|
+
const view = this.dataView();
|
|
651
|
+
const variablePtr = view.getUint32(out, true);
|
|
652
|
+
const variableLen = view.getUint32(out + 4, true);
|
|
653
|
+
const messagePtr = view.getUint32(out + 8, true);
|
|
654
|
+
const messageLen = view.getUint32(out + 12, true);
|
|
655
|
+
if (variablePtr === 0 || messagePtr === 0)
|
|
656
|
+
return null;
|
|
657
|
+
return [
|
|
658
|
+
textDecoder.decode(this.readBytes(variablePtr, variableLen)),
|
|
659
|
+
textDecoder.decode(this.readBytes(messagePtr, messageLen)),
|
|
660
|
+
];
|
|
661
|
+
}
|
|
662
|
+
finally {
|
|
663
|
+
this.free(out, 16);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
nodeSymbolName(handle, node) {
|
|
667
|
+
const session = handle;
|
|
668
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_node_symbol_name(session, asI64(node), data, len));
|
|
669
|
+
}
|
|
670
|
+
nodeText(handle, node) {
|
|
671
|
+
const session = handle;
|
|
672
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_node_text(session, asI64(node), data, len));
|
|
673
|
+
}
|
|
674
|
+
nodeSpan(handle, node) {
|
|
675
|
+
const out = this.malloc(16);
|
|
676
|
+
try {
|
|
677
|
+
const status = this.wasm.galley_node_span(handle, asI64(node), out, out + 8);
|
|
678
|
+
if (isNegative(status))
|
|
679
|
+
return null;
|
|
680
|
+
const view = this.dataView();
|
|
681
|
+
return [view.getBigUint64(out, true), view.getBigUint64(out + 8, true)];
|
|
682
|
+
}
|
|
683
|
+
finally {
|
|
684
|
+
this.free(out, 16);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
nodeLineColumn(handle, node) {
|
|
688
|
+
const out = this.malloc(8);
|
|
689
|
+
try {
|
|
690
|
+
const status = this.wasm.galley_node_line_column(handle, asI64(node), out, out + 4);
|
|
691
|
+
if (isNegative(status))
|
|
692
|
+
return null;
|
|
693
|
+
const view = this.dataView();
|
|
694
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
695
|
+
}
|
|
696
|
+
finally {
|
|
697
|
+
this.free(out, 8);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
nodeVariableIndex(handle, node) {
|
|
701
|
+
return toNumber(this.wasm.galley_node_variable_index(handle, asI64(node)));
|
|
702
|
+
}
|
|
703
|
+
symbolNameAt(handle, index) {
|
|
704
|
+
const session = handle;
|
|
705
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_symbol_name(session, BigInt(index), data, len));
|
|
706
|
+
}
|
|
707
|
+
symbolIsTerminal(handle, index) {
|
|
708
|
+
return this.wasm.galley_symbol_is_terminal(handle, BigInt(index)) !== 0;
|
|
709
|
+
}
|
|
710
|
+
variableNameAt(handle, index) {
|
|
711
|
+
const session = handle;
|
|
712
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_variable_name(session, BigInt(index), data, len));
|
|
713
|
+
}
|
|
714
|
+
// -- diagnostics ------------------------------------------------------------------
|
|
715
|
+
hasDiagnostic(handle) {
|
|
716
|
+
return this.wasm.galley_has_diagnostic(handle) !== 0;
|
|
717
|
+
}
|
|
718
|
+
diagnosticKind(handle) {
|
|
719
|
+
return toNumber(this.wasm.galley_diagnostic_kind(handle));
|
|
720
|
+
}
|
|
721
|
+
diagnosticMessage(handle) {
|
|
722
|
+
const out = this.malloc(4);
|
|
723
|
+
try {
|
|
724
|
+
if (this.wasm.galley_diagnostic_message(handle, out) !== 0n)
|
|
725
|
+
return null;
|
|
726
|
+
return this.readCString(this.dataView().getUint32(out, true));
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
this.free(out, 4);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
diagnosticMessageAnsi(handle) {
|
|
733
|
+
const out = this.malloc(4);
|
|
734
|
+
try {
|
|
735
|
+
if (this.wasm.galley_diagnostic_message_ansi(handle, out) !== 0n)
|
|
736
|
+
return null;
|
|
737
|
+
return this.readCString(this.dataView().getUint32(out, true));
|
|
738
|
+
}
|
|
739
|
+
finally {
|
|
740
|
+
this.free(out, 4);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
diagnosticPosition(handle) {
|
|
744
|
+
const out = this.malloc(8);
|
|
745
|
+
try {
|
|
746
|
+
if (isNegative(this.wasm.galley_diagnostic_position(handle, out, out + 4)))
|
|
747
|
+
return null;
|
|
748
|
+
const view = this.dataView();
|
|
749
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
750
|
+
}
|
|
751
|
+
finally {
|
|
752
|
+
this.free(out, 8);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
diagnosticUnexpectedToken(handle) {
|
|
756
|
+
const session = handle;
|
|
757
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_diagnostic_unexpected_token(session, data, len));
|
|
758
|
+
}
|
|
759
|
+
diagnosticExpectedCount(handle) {
|
|
760
|
+
return toNumber(this.wasm.galley_diagnostic_expected_count(handle));
|
|
761
|
+
}
|
|
762
|
+
diagnosticExpectedAt(handle, index) {
|
|
763
|
+
const session = handle;
|
|
764
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_diagnostic_expected_at(session, BigInt(index), data, len));
|
|
765
|
+
}
|
|
766
|
+
diagnosticContextCount(handle) {
|
|
767
|
+
return toNumber(this.wasm.galley_diagnostic_context_count(handle));
|
|
768
|
+
}
|
|
769
|
+
diagnosticContextAt(handle, index) {
|
|
770
|
+
const session = handle;
|
|
771
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_diagnostic_context_at(session, BigInt(index), data, len));
|
|
772
|
+
}
|
|
773
|
+
syntaxErrorCount(handle) {
|
|
774
|
+
return toNumber(this.wasm.galley_syntax_error_count(handle));
|
|
775
|
+
}
|
|
776
|
+
semanticErrorCount(handle) {
|
|
777
|
+
return toNumber(this.wasm.galley_semantic_error_count(handle));
|
|
778
|
+
}
|
|
779
|
+
diagnosticSemantic(handle) {
|
|
780
|
+
const session = handle;
|
|
781
|
+
return this.readSemanticPair((variable, variableLen, message, messageLen) => this.wasm.galley_diagnostic_semantic(session, variable, variableLen, message, messageLen));
|
|
782
|
+
}
|
|
783
|
+
diagnosticIndentation(handle) {
|
|
784
|
+
const out = this.malloc(8);
|
|
785
|
+
try {
|
|
786
|
+
if (toNumber(this.wasm.galley_diagnostic_indentation(handle, out, out + 4)) !== 0)
|
|
787
|
+
return null;
|
|
788
|
+
const view = this.dataView();
|
|
789
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
790
|
+
}
|
|
791
|
+
finally {
|
|
792
|
+
this.free(out, 8);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
diagnosticRecoveryKind(handle) {
|
|
796
|
+
return toNumber(this.wasm.galley_diagnostic_recovery_kind(handle));
|
|
797
|
+
}
|
|
798
|
+
diagnosticRecoveryTerminal(handle) {
|
|
799
|
+
const session = handle;
|
|
800
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_diagnostic_recovery_terminal(session, data, len));
|
|
801
|
+
}
|
|
802
|
+
diagnosticRecoveryResume(handle) {
|
|
803
|
+
const out = this.malloc(8);
|
|
804
|
+
try {
|
|
805
|
+
if (toNumber(this.wasm.galley_diagnostic_recovery_resume(handle, out)) !== 0)
|
|
806
|
+
return null;
|
|
807
|
+
return toNumber(this.dataView().getBigInt64(out, true));
|
|
808
|
+
}
|
|
809
|
+
finally {
|
|
810
|
+
this.free(out, 8);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
diagnosticRecoveryLhsVariable(handle) {
|
|
814
|
+
const session = handle;
|
|
815
|
+
const out = this.malloc(8);
|
|
816
|
+
try {
|
|
817
|
+
const status = this.wasm.galley_diagnostic_recovery_lhs_variable(session, out, out + 4);
|
|
818
|
+
if (isNegative(status))
|
|
819
|
+
return null;
|
|
820
|
+
const view = this.dataView();
|
|
821
|
+
const ptr = view.getUint32(out, true);
|
|
822
|
+
if (ptr === 0)
|
|
823
|
+
return null;
|
|
824
|
+
return textDecoder.decode(this.readBytes(ptr, view.getUint32(out + 4, true)));
|
|
825
|
+
}
|
|
826
|
+
finally {
|
|
827
|
+
this.free(out, 8);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
diagnosticRecoveryProduction(handle) {
|
|
831
|
+
const out = this.malloc(12);
|
|
832
|
+
try {
|
|
833
|
+
if (toNumber(this.wasm.galley_diagnostic_recovery_production(handle, out, out + 4, out + 8)) !==
|
|
834
|
+
0)
|
|
835
|
+
return null;
|
|
836
|
+
const view = this.dataView();
|
|
837
|
+
return [
|
|
838
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
839
|
+
view.getUint32(out + 8, true),
|
|
840
|
+
];
|
|
841
|
+
}
|
|
842
|
+
finally {
|
|
843
|
+
this.free(out, 12);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
diagnosticRecoveryOccurrence(handle) {
|
|
847
|
+
const out = this.malloc(24);
|
|
848
|
+
try {
|
|
849
|
+
if (toNumber(this.wasm.galley_diagnostic_recovery_occurrence(handle, out, out + 4, out + 8, out + 12, out + 16, out + 20)) !== 0)
|
|
850
|
+
return null;
|
|
851
|
+
const view = this.dataView();
|
|
852
|
+
return [
|
|
853
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
854
|
+
view.getUint32(out + 8, true),
|
|
855
|
+
view.getUint32(out + 12, true),
|
|
856
|
+
textDecoder.decode(this.readBytes(view.getUint32(out + 16, true), view.getUint32(out + 20, true))),
|
|
857
|
+
];
|
|
858
|
+
}
|
|
859
|
+
finally {
|
|
860
|
+
this.free(out, 24);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
recordedDiagnosticCount(handle) {
|
|
864
|
+
return toNumber(this.wasm.galley_recorded_diagnostic_count(handle));
|
|
865
|
+
}
|
|
866
|
+
recordedDiagnosticKind(handle, diagIndex) {
|
|
867
|
+
return toNumber(this.wasm.galley_recorded_diagnostic_kind(handle, BigInt(diagIndex)));
|
|
868
|
+
}
|
|
869
|
+
recordedDiagnosticPosition(handle, diagIndex) {
|
|
870
|
+
const out = this.malloc(8);
|
|
871
|
+
try {
|
|
872
|
+
if (isNegative(this.wasm.galley_recorded_diagnostic_position(handle, BigInt(diagIndex), out, out + 4)))
|
|
873
|
+
return null;
|
|
874
|
+
const view = this.dataView();
|
|
875
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
876
|
+
}
|
|
877
|
+
finally {
|
|
878
|
+
this.free(out, 8);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
recordedUnexpectedToken(handle, diagIndex) {
|
|
882
|
+
const session = handle;
|
|
883
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_recorded_unexpected_token(session, BigInt(diagIndex), data, len));
|
|
884
|
+
}
|
|
885
|
+
recordedDiagnosticMessage(handle, diagIndex) {
|
|
886
|
+
const out = this.malloc(4);
|
|
887
|
+
try {
|
|
888
|
+
if (toNumber(this.wasm.galley_recorded_diagnostic_message(handle, BigInt(diagIndex), out)) !== 0)
|
|
889
|
+
return null;
|
|
890
|
+
return this.readCString(this.dataView().getUint32(out, true));
|
|
891
|
+
}
|
|
892
|
+
finally {
|
|
893
|
+
this.free(out, 4);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
recordedIndentation(handle, diagIndex) {
|
|
897
|
+
const out = this.malloc(8);
|
|
898
|
+
try {
|
|
899
|
+
if (toNumber(this.wasm.galley_recorded_indentation(handle, BigInt(diagIndex), out, out + 4)) !== 0)
|
|
900
|
+
return null;
|
|
901
|
+
const view = this.dataView();
|
|
902
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
903
|
+
}
|
|
904
|
+
finally {
|
|
905
|
+
this.free(out, 8);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
recordedSemantic(handle, diagIndex) {
|
|
909
|
+
const session = handle;
|
|
910
|
+
return this.readSemanticPair((variable, variableLen, message, messageLen) => this.wasm.galley_recorded_semantic(session, BigInt(diagIndex), variable, variableLen, message, messageLen));
|
|
911
|
+
}
|
|
912
|
+
recordedExpectedCount(handle, diagIndex) {
|
|
913
|
+
return toNumber(this.wasm.galley_recorded_expected_count(handle, BigInt(diagIndex)));
|
|
914
|
+
}
|
|
915
|
+
recordedExpectedToken(handle, diagIndex, tokenIndex) {
|
|
916
|
+
const session = handle;
|
|
917
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_recorded_expected_token(session, BigInt(diagIndex), BigInt(tokenIndex), data, len));
|
|
918
|
+
}
|
|
919
|
+
recordedContextCount(handle, diagIndex) {
|
|
920
|
+
return toNumber(this.wasm.galley_recorded_context_count(handle, BigInt(diagIndex)));
|
|
921
|
+
}
|
|
922
|
+
recordedContextName(handle, diagIndex, contextIndex) {
|
|
923
|
+
const session = handle;
|
|
924
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_recorded_context_name(session, BigInt(diagIndex), BigInt(contextIndex), data, len));
|
|
925
|
+
}
|
|
926
|
+
recordedRecoveryKind(handle, diagIndex) {
|
|
927
|
+
return toNumber(this.wasm.galley_recorded_diagnostic_recovery_kind(handle, BigInt(diagIndex)));
|
|
928
|
+
}
|
|
929
|
+
recordedRecoveryTerminal(handle, diagIndex) {
|
|
930
|
+
const session = handle;
|
|
931
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_recorded_recovery_terminal(session, BigInt(diagIndex), data, len));
|
|
932
|
+
}
|
|
933
|
+
recordedRecoveryResume(handle, diagIndex) {
|
|
934
|
+
const out = this.malloc(8);
|
|
935
|
+
try {
|
|
936
|
+
if (toNumber(this.wasm.galley_recorded_recovery_resume(handle, BigInt(diagIndex), out)) !== 0)
|
|
937
|
+
return null;
|
|
938
|
+
return toNumber(this.dataView().getBigInt64(out, true));
|
|
939
|
+
}
|
|
940
|
+
finally {
|
|
941
|
+
this.free(out, 8);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
recordedRecoveryLhsVariable(handle, diagIndex) {
|
|
945
|
+
const session = handle;
|
|
946
|
+
const out = this.malloc(8);
|
|
947
|
+
try {
|
|
948
|
+
const status = this.wasm.galley_recorded_recovery_lhs_variable(session, BigInt(diagIndex), out, out + 4);
|
|
949
|
+
if (isNegative(status))
|
|
950
|
+
return null;
|
|
951
|
+
const view = this.dataView();
|
|
952
|
+
const ptr = view.getUint32(out, true);
|
|
953
|
+
if (ptr === 0)
|
|
954
|
+
return null;
|
|
955
|
+
return textDecoder.decode(this.readBytes(ptr, view.getUint32(out + 4, true)));
|
|
956
|
+
}
|
|
957
|
+
finally {
|
|
958
|
+
this.free(out, 8);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
recordedRecoveryProduction(handle, diagIndex) {
|
|
962
|
+
const out = this.malloc(12);
|
|
963
|
+
try {
|
|
964
|
+
if (toNumber(this.wasm.galley_recorded_recovery_production(handle, BigInt(diagIndex), out, out + 4, out + 8)) !== 0)
|
|
965
|
+
return null;
|
|
966
|
+
const view = this.dataView();
|
|
967
|
+
return [
|
|
968
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
969
|
+
view.getUint32(out + 8, true),
|
|
970
|
+
];
|
|
971
|
+
}
|
|
972
|
+
finally {
|
|
973
|
+
this.free(out, 12);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
recordedRecoveryOccurrence(handle, diagIndex) {
|
|
977
|
+
const out = this.malloc(24);
|
|
978
|
+
try {
|
|
979
|
+
if (toNumber(this.wasm.galley_recorded_recovery_occurrence(handle, BigInt(diagIndex), out, out + 4, out + 8, out + 12, out + 16, out + 20)) !== 0)
|
|
980
|
+
return null;
|
|
981
|
+
const view = this.dataView();
|
|
982
|
+
return [
|
|
983
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
984
|
+
view.getUint32(out + 8, true),
|
|
985
|
+
view.getUint32(out + 12, true),
|
|
986
|
+
textDecoder.decode(this.readBytes(view.getUint32(out + 16, true), view.getUint32(out + 20, true))),
|
|
987
|
+
];
|
|
988
|
+
}
|
|
989
|
+
finally {
|
|
990
|
+
this.free(out, 24);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
// -- tree editing --------------------------------------------------------------------
|
|
994
|
+
treeAppendChildren(handle, parent, first) {
|
|
995
|
+
return toNumber(this.wasm.galley_tree_append_children(handle, asI64(parent), asI64(first)));
|
|
996
|
+
}
|
|
997
|
+
treeInsertBefore(handle, target, first) {
|
|
998
|
+
return toNumber(this.wasm.galley_tree_insert_before(handle, asI64(target), asI64(first)));
|
|
999
|
+
}
|
|
1000
|
+
treeInsertAfter(handle, target, first) {
|
|
1001
|
+
return toNumber(this.wasm.galley_tree_insert_after(handle, asI64(target), asI64(first)));
|
|
1002
|
+
}
|
|
1003
|
+
treeRemoveSiblings(handle, node, count) {
|
|
1004
|
+
const out = this.malloc(8);
|
|
1005
|
+
try {
|
|
1006
|
+
const status = toNumber(this.wasm.galley_tree_remove_siblings(handle, asI64(node), count, out));
|
|
1007
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1008
|
+
}
|
|
1009
|
+
finally {
|
|
1010
|
+
this.free(out, 8);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
treeRemoveSelf(handle, node) {
|
|
1014
|
+
const out = this.malloc(8);
|
|
1015
|
+
try {
|
|
1016
|
+
const status = toNumber(this.wasm.galley_tree_remove_self(handle, asI64(node), out));
|
|
1017
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1018
|
+
}
|
|
1019
|
+
finally {
|
|
1020
|
+
this.free(out, 8);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
treePromoteChildrenOverWrapper(handle, wrapper) {
|
|
1024
|
+
const out = this.malloc(8);
|
|
1025
|
+
try {
|
|
1026
|
+
const status = toNumber(this.wasm.galley_tree_promote_children_over_wrapper(handle, asI64(wrapper), out));
|
|
1027
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1028
|
+
}
|
|
1029
|
+
finally {
|
|
1030
|
+
this.free(out, 8);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
treeCleanChildren(handle, node) {
|
|
1034
|
+
const out = this.malloc(8);
|
|
1035
|
+
try {
|
|
1036
|
+
const status = toNumber(this.wasm.galley_tree_clean_children(handle, asI64(node), out));
|
|
1037
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1038
|
+
}
|
|
1039
|
+
finally {
|
|
1040
|
+
this.free(out, 8);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
treeUnlinkWrapper(handle, wrapper) {
|
|
1044
|
+
return toNumber(this.wasm.galley_tree_unlink_wrapper(handle, asI64(wrapper)));
|
|
1045
|
+
}
|
|
1046
|
+
treeInsertChildrenAt(handle, parent, index, first) {
|
|
1047
|
+
return toNumber(this.wasm.galley_tree_insert_children_at(handle, asI64(parent), index, asI64(first)));
|
|
1048
|
+
}
|
|
1049
|
+
treeRemoveChildrenAt(handle, parent, index, count) {
|
|
1050
|
+
const out = this.malloc(8);
|
|
1051
|
+
try {
|
|
1052
|
+
const status = toNumber(this.wasm.galley_tree_remove_children_at(handle, asI64(parent), index, count, out));
|
|
1053
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1054
|
+
}
|
|
1055
|
+
finally {
|
|
1056
|
+
this.free(out, 8);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
// -- procedure hooks (parse-time state) ---------------------------------------------------
|
|
1060
|
+
procCurrentNode(args) {
|
|
1061
|
+
return asAddress(this.wasm.galley_procedure_current_node(args));
|
|
1062
|
+
}
|
|
1063
|
+
procSetCurrentNode(args, node) {
|
|
1064
|
+
this.wasm.galley_procedure_set_current_node(args, asI64(node));
|
|
1065
|
+
}
|
|
1066
|
+
procDropSelf(args) {
|
|
1067
|
+
return toNumber(this.wasm.galley_procedure_drop_self(args));
|
|
1068
|
+
}
|
|
1069
|
+
procDropChildren(args) {
|
|
1070
|
+
return toNumber(this.wasm.galley_procedure_drop_children(args));
|
|
1071
|
+
}
|
|
1072
|
+
procDropIfEmpty(args) {
|
|
1073
|
+
return toNumber(this.wasm.galley_procedure_drop_if_empty(args));
|
|
1074
|
+
}
|
|
1075
|
+
procReplaceWithChildren(args) {
|
|
1076
|
+
return toNumber(this.wasm.galley_procedure_replace_with_children(args));
|
|
1077
|
+
}
|
|
1078
|
+
procContextLine(args) {
|
|
1079
|
+
return this.wasm.galley_procedure_context_line(args);
|
|
1080
|
+
}
|
|
1081
|
+
procContextColumn(args) {
|
|
1082
|
+
return this.wasm.galley_procedure_context_column(args);
|
|
1083
|
+
}
|
|
1084
|
+
procReportSemanticError(args, message) {
|
|
1085
|
+
const bytes = textEncoder.encode(textDecoder.decode(message));
|
|
1086
|
+
const slot = this.writeBytes(bytes);
|
|
1087
|
+
try {
|
|
1088
|
+
return toNumber(this.wasm.galley_procedure_report_semantic_error(args, slot.ptr, slot.len));
|
|
1089
|
+
}
|
|
1090
|
+
finally {
|
|
1091
|
+
this.free(slot.ptr, Math.max(slot.len, 1));
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
syncProcedures(names) {
|
|
1095
|
+
if (typeof this.wasm.galley_js_procedure_clear !== "function")
|
|
1096
|
+
return;
|
|
1097
|
+
if (typeof this.wasm.galley_js_procedure_enable !== "function")
|
|
1098
|
+
return;
|
|
1099
|
+
this.wasm.galley_js_procedure_clear();
|
|
1100
|
+
for (const name of names) {
|
|
1101
|
+
const bytes = textEncoder.encode(name);
|
|
1102
|
+
const slot = this.writeBytes(bytes);
|
|
1103
|
+
try {
|
|
1104
|
+
this.wasm.galley_js_procedure_enable(slot.ptr, slot.len);
|
|
1105
|
+
}
|
|
1106
|
+
finally {
|
|
1107
|
+
this.free(slot.ptr, Math.max(slot.len, 1));
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
// Warm the ID table outside any parse so the hot path never queries
|
|
1111
|
+
// (querying would re-enter the guest mid-parse).
|
|
1112
|
+
this.procedureNames();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
//# sourceMappingURL=ffi.js.map
|