@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/src/ffi.ts
ADDED
|
@@ -0,0 +1,1526 @@
|
|
|
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
|
+
|
|
21
|
+
import * as fs from "node:fs";
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
import process from "node:process";
|
|
24
|
+
import type {
|
|
25
|
+
FfiPort,
|
|
26
|
+
Handle,
|
|
27
|
+
SessionCOptions,
|
|
28
|
+
TreeSnapshot,
|
|
29
|
+
WalkedStep,
|
|
30
|
+
} from "@sanbus/galley-core";
|
|
31
|
+
import { GalleyError, dispatchProcedure, resolveArtifact, wasmArtifactFileName } from "@sanbus/galley-core";
|
|
32
|
+
|
|
33
|
+
const LIBRARY_BASE = "galley-js-wasm";
|
|
34
|
+
const WASI_NOSYS = 52;
|
|
35
|
+
const WASI_BADF = 8;
|
|
36
|
+
|
|
37
|
+
export class NeedInitError extends Error {
|
|
38
|
+
constructor(libraryPath?: string) {
|
|
39
|
+
super(
|
|
40
|
+
`galley-wasm: WebAssembly module${libraryPath ? ` for ${libraryPath}` : ""} is not initialized. ` +
|
|
41
|
+
`Call "await init()" (or "await init({ url })" / "init({ bytes })" in browsers) first.`,
|
|
42
|
+
);
|
|
43
|
+
this.name = "NeedInitError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Callable view of the wasm exports (wasm32: pointers/i32 are `number`, i64/u64 are `bigint`). */
|
|
48
|
+
interface GalleyWasmExports {
|
|
49
|
+
memory: WebAssembly.Memory;
|
|
50
|
+
_initialize?: unknown;
|
|
51
|
+
galley_js_malloc(len: number): number;
|
|
52
|
+
galley_js_free(ptr: number, len: number): void;
|
|
53
|
+
galley_version(): number;
|
|
54
|
+
galley_parser_type(): bigint;
|
|
55
|
+
galley_error_recovery_mode(): bigint;
|
|
56
|
+
galley_has_ast(): number;
|
|
57
|
+
galley_has_procedures(): number;
|
|
58
|
+
galley_allows_no_ast_tree_procedures(): number;
|
|
59
|
+
galley_source_retention_enabled(): number;
|
|
60
|
+
galley_has_position_tracking(): number;
|
|
61
|
+
galley_has_input_streaming(): number;
|
|
62
|
+
galley_uses_verbatim(): number;
|
|
63
|
+
galley_stack_overflow_recovery_available(): number;
|
|
64
|
+
galley_symbol_count(): bigint;
|
|
65
|
+
galley_variable_count(): bigint;
|
|
66
|
+
galley_status_string(status: bigint): number;
|
|
67
|
+
galley_symbol_name(session: number, index: bigint, outData: number, outLen: number): bigint;
|
|
68
|
+
galley_symbol_is_terminal(session: number, index: bigint): number;
|
|
69
|
+
galley_variable_name(session: number, index: bigint, outData: number, outLen: number): bigint;
|
|
70
|
+
galley_session_create(): number;
|
|
71
|
+
galley_session_create_ex(options: number): number;
|
|
72
|
+
galley_session_destroy(session: number): void;
|
|
73
|
+
galley_session_set_message_override(
|
|
74
|
+
session: number,
|
|
75
|
+
name: number,
|
|
76
|
+
nameLen: number,
|
|
77
|
+
message: number,
|
|
78
|
+
messageLen: number,
|
|
79
|
+
): bigint;
|
|
80
|
+
galley_parse(session: number, data: number, len: number): bigint;
|
|
81
|
+
galley_node_count(session: number): bigint;
|
|
82
|
+
galley_reserve_nodes(session: number, capacity: bigint): bigint;
|
|
83
|
+
galley_node_capacity(session: number): bigint;
|
|
84
|
+
galley_root_node(session: number): bigint;
|
|
85
|
+
galley_node_is_valid(session: number, node: bigint): number;
|
|
86
|
+
galley_node_child_count(session: number, node: bigint): number;
|
|
87
|
+
galley_node_first_child(session: number, node: bigint): bigint;
|
|
88
|
+
galley_node_last_child(session: number, node: bigint): bigint;
|
|
89
|
+
galley_node_next_sibling(session: number, node: bigint): bigint;
|
|
90
|
+
galley_node_prior_sibling(session: number, node: bigint): bigint;
|
|
91
|
+
galley_node_parent(session: number, node: bigint): bigint;
|
|
92
|
+
galley_tree_snapshot(
|
|
93
|
+
session: number,
|
|
94
|
+
outParent: number,
|
|
95
|
+
outFirstChild: number,
|
|
96
|
+
outNext: number,
|
|
97
|
+
outChildCount: number,
|
|
98
|
+
outVariable: number,
|
|
99
|
+
outSpanStart: number,
|
|
100
|
+
outSpanLen: number,
|
|
101
|
+
capacity: bigint,
|
|
102
|
+
): bigint;
|
|
103
|
+
galley_walker_create(session: number, node: bigint, skipSemanticErrors: number): number;
|
|
104
|
+
galley_walker_next(walker: number, outNode: number, outDepth: number, outFlag: number): number;
|
|
105
|
+
galley_walker_skip_children(walker: number): void;
|
|
106
|
+
galley_walker_destroy(walker: number): void;
|
|
107
|
+
galley_node_symbol_name(session: number, node: bigint, outData: number, outLen: number): bigint;
|
|
108
|
+
galley_node_text(session: number, node: bigint, outData: number, outLen: number): bigint;
|
|
109
|
+
galley_node_span(session: number, node: bigint, outStart: number, outLen: number): bigint;
|
|
110
|
+
galley_node_line_column(session: number, node: bigint, outLine: number, outCol: number): bigint;
|
|
111
|
+
galley_node_variable_index(session: number, node: bigint): bigint;
|
|
112
|
+
galley_last_position(session: number, outLine: number, outCol: number): bigint;
|
|
113
|
+
galley_has_diagnostic(session: number): number;
|
|
114
|
+
galley_diagnostic_kind(session: number): bigint;
|
|
115
|
+
galley_diagnostic_message(session: number, out: number): bigint;
|
|
116
|
+
galley_diagnostic_message_ansi(session: number, out: number): bigint;
|
|
117
|
+
galley_diagnostic_position(session: number, outLine: number, outCol: number): bigint;
|
|
118
|
+
galley_diagnostic_unexpected_token(session: number, outData: number, outLen: number): bigint;
|
|
119
|
+
galley_diagnostic_expected_count(session: number): bigint;
|
|
120
|
+
galley_diagnostic_expected_at(session: number, index: bigint, outData: number, outLen: number): bigint;
|
|
121
|
+
galley_diagnostic_context_count(session: number): bigint;
|
|
122
|
+
galley_diagnostic_context_at(session: number, index: bigint, outData: number, outLen: number): bigint;
|
|
123
|
+
galley_diagnostic_indentation(session: number, outSpaces: number, outWidth: number): bigint;
|
|
124
|
+
galley_syntax_error_count(session: number): bigint;
|
|
125
|
+
galley_semantic_error_count(session: number): bigint;
|
|
126
|
+
galley_diagnostic_semantic(
|
|
127
|
+
session: number,
|
|
128
|
+
outVariable: number,
|
|
129
|
+
outVariableLen: number,
|
|
130
|
+
outMessage: number,
|
|
131
|
+
outMessageLen: number,
|
|
132
|
+
): bigint;
|
|
133
|
+
galley_diagnostic_recovery_kind(session: number): bigint;
|
|
134
|
+
galley_diagnostic_recovery_terminal(session: number, outData: number, outLen: number): bigint;
|
|
135
|
+
galley_diagnostic_recovery_resume(session: number, out: number): bigint;
|
|
136
|
+
galley_diagnostic_recovery_lhs_variable(session: number, outData: number, outLen: number): bigint;
|
|
137
|
+
galley_diagnostic_recovery_production(
|
|
138
|
+
session: number,
|
|
139
|
+
outVar: number,
|
|
140
|
+
outLen: number,
|
|
141
|
+
outIndex: number,
|
|
142
|
+
): bigint;
|
|
143
|
+
galley_diagnostic_recovery_occurrence(
|
|
144
|
+
session: number,
|
|
145
|
+
outParent: number,
|
|
146
|
+
outParentLen: number,
|
|
147
|
+
outRhs: number,
|
|
148
|
+
outSym: number,
|
|
149
|
+
outVar: number,
|
|
150
|
+
outVarLen: number,
|
|
151
|
+
): bigint;
|
|
152
|
+
galley_recorded_diagnostic_count(session: number): bigint;
|
|
153
|
+
galley_recorded_diagnostic_kind(session: number, index: bigint): bigint;
|
|
154
|
+
galley_recorded_diagnostic_position(
|
|
155
|
+
session: number,
|
|
156
|
+
index: bigint,
|
|
157
|
+
outLine: number,
|
|
158
|
+
outCol: number,
|
|
159
|
+
): bigint;
|
|
160
|
+
galley_recorded_unexpected_token(
|
|
161
|
+
session: number,
|
|
162
|
+
index: bigint,
|
|
163
|
+
outData: number,
|
|
164
|
+
outLen: number,
|
|
165
|
+
): bigint;
|
|
166
|
+
galley_recorded_diagnostic_message(session: number, index: bigint, out: number): bigint;
|
|
167
|
+
galley_recorded_indentation(
|
|
168
|
+
session: number,
|
|
169
|
+
index: bigint,
|
|
170
|
+
outSpaces: number,
|
|
171
|
+
outWidth: number,
|
|
172
|
+
): bigint;
|
|
173
|
+
galley_recorded_semantic(
|
|
174
|
+
session: number,
|
|
175
|
+
index: bigint,
|
|
176
|
+
outVariable: number,
|
|
177
|
+
outVariableLen: number,
|
|
178
|
+
outMessage: number,
|
|
179
|
+
outMessageLen: number,
|
|
180
|
+
): bigint;
|
|
181
|
+
galley_recorded_expected_count(session: number, index: bigint): bigint;
|
|
182
|
+
galley_recorded_expected_token(
|
|
183
|
+
session: number,
|
|
184
|
+
index: bigint,
|
|
185
|
+
tokenIndex: bigint,
|
|
186
|
+
outData: number,
|
|
187
|
+
outLen: number,
|
|
188
|
+
): bigint;
|
|
189
|
+
galley_recorded_context_count(session: number, index: bigint): bigint;
|
|
190
|
+
galley_recorded_context_name(
|
|
191
|
+
session: number,
|
|
192
|
+
index: bigint,
|
|
193
|
+
contextIndex: bigint,
|
|
194
|
+
outData: number,
|
|
195
|
+
outLen: number,
|
|
196
|
+
): bigint;
|
|
197
|
+
galley_recorded_diagnostic_recovery_kind(session: number, index: bigint): bigint;
|
|
198
|
+
galley_recorded_recovery_terminal(
|
|
199
|
+
session: number,
|
|
200
|
+
index: bigint,
|
|
201
|
+
outData: number,
|
|
202
|
+
outLen: number,
|
|
203
|
+
): bigint;
|
|
204
|
+
galley_recorded_recovery_resume(session: number, index: bigint, out: number): bigint;
|
|
205
|
+
galley_recorded_recovery_lhs_variable(
|
|
206
|
+
session: number,
|
|
207
|
+
index: bigint,
|
|
208
|
+
outData: number,
|
|
209
|
+
outLen: number,
|
|
210
|
+
): bigint;
|
|
211
|
+
galley_recorded_recovery_production(
|
|
212
|
+
session: number,
|
|
213
|
+
index: bigint,
|
|
214
|
+
outVar: number,
|
|
215
|
+
outLen: number,
|
|
216
|
+
outIdx: number,
|
|
217
|
+
): bigint;
|
|
218
|
+
galley_recorded_recovery_occurrence(
|
|
219
|
+
session: number,
|
|
220
|
+
index: bigint,
|
|
221
|
+
outParent: number,
|
|
222
|
+
outParentLen: number,
|
|
223
|
+
outRhs: number,
|
|
224
|
+
outSym: number,
|
|
225
|
+
outVar: number,
|
|
226
|
+
outVarLen: number,
|
|
227
|
+
): bigint;
|
|
228
|
+
galley_tree_append_children(session: number, parent: bigint, first: bigint): bigint;
|
|
229
|
+
galley_tree_insert_before(session: number, target: bigint, first: bigint): bigint;
|
|
230
|
+
galley_tree_insert_after(session: number, target: bigint, first: bigint): bigint;
|
|
231
|
+
galley_tree_remove_siblings(session: number, node: bigint, count: number, outHead: number): bigint;
|
|
232
|
+
galley_tree_remove_self(session: number, node: bigint, outHead: number): bigint;
|
|
233
|
+
galley_tree_promote_children_over_wrapper(session: number, wrapper: bigint, outHead: number): bigint;
|
|
234
|
+
galley_tree_clean_children(session: number, node: bigint, outHead: number): bigint;
|
|
235
|
+
galley_tree_unlink_wrapper(session: number, wrapper: bigint): bigint;
|
|
236
|
+
galley_tree_insert_children_at(session: number, parent: bigint, index: number, first: bigint): bigint;
|
|
237
|
+
galley_tree_remove_children_at(
|
|
238
|
+
session: number,
|
|
239
|
+
parent: bigint,
|
|
240
|
+
index: number,
|
|
241
|
+
count: number,
|
|
242
|
+
outHead: number,
|
|
243
|
+
): bigint;
|
|
244
|
+
galley_procedure_current_node(args: number): bigint;
|
|
245
|
+
galley_procedure_set_current_node(args: number, node: bigint): void;
|
|
246
|
+
galley_procedure_drop_self(args: number): bigint;
|
|
247
|
+
galley_procedure_drop_children(args: number): bigint;
|
|
248
|
+
galley_procedure_drop_if_empty(args: number): bigint;
|
|
249
|
+
galley_procedure_replace_with_children(args: number): bigint;
|
|
250
|
+
galley_procedure_context_line(args: number): number;
|
|
251
|
+
galley_procedure_context_column(args: number): number;
|
|
252
|
+
galley_procedure_report_semantic_error(args: number, message: number, messageLen: number): bigint;
|
|
253
|
+
galley_js_procedure_count?(): number;
|
|
254
|
+
galley_js_procedure_name_ptr?(index: number): number;
|
|
255
|
+
galley_js_procedure_name_len?(index: number): number;
|
|
256
|
+
galley_js_procedure_enable?(namePtr: number, nameLen: number): number;
|
|
257
|
+
galley_js_procedure_clear?(): void;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// --- instance cache (one module per grammar file) --------------------------
|
|
261
|
+
|
|
262
|
+
const ports = new Map<string, WasmPort>();
|
|
263
|
+
let seededDefault: string | null = null;
|
|
264
|
+
|
|
265
|
+
function isNode(): boolean {
|
|
266
|
+
return (
|
|
267
|
+
typeof process !== "undefined" &&
|
|
268
|
+
typeof (process as unknown as { versions?: { node?: string } }).versions?.node === "string"
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- library discovery (mirrors the Node adapter, `.wasm` names) -----------
|
|
273
|
+
// One place, named up front: an explicit path or GALLEY_LIBRARY_PATH.
|
|
274
|
+
// Anything else is a loud error, never a search.
|
|
275
|
+
|
|
276
|
+
const BUILD_HINT =
|
|
277
|
+
`Build it first: npx galley-js-wasm <language-dir>\n` +
|
|
278
|
+
`or set GALLEY_LIBRARY_PATH=/path/to/${wasmFileName()}`;
|
|
279
|
+
|
|
280
|
+
export function wasmFileName(base = LIBRARY_BASE): string {
|
|
281
|
+
return wasmArtifactFileName(base);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function exists(localPath: string): boolean {
|
|
285
|
+
try {
|
|
286
|
+
fs.accessSync(localPath);
|
|
287
|
+
return true;
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function findLibrary(explicit?: string): string {
|
|
294
|
+
return resolveArtifact(explicit, {
|
|
295
|
+
getEnv: (name) => process.env[name],
|
|
296
|
+
resolvePath: (candidate) => path.resolve(candidate),
|
|
297
|
+
existsSync: exists,
|
|
298
|
+
buildHint: BUILD_HINT,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --- minimal WASI stub ------------------------------------------------------
|
|
303
|
+
// Real entropy and clocks; filesystem calls report unavailable. The parse
|
|
304
|
+
// path never touches the filesystem (`parseFile` is served by the host
|
|
305
|
+
// reading the file into a buffer first).
|
|
306
|
+
|
|
307
|
+
function makeWasiStub(getMemory: () => ArrayBuffer): Record<string, WebAssembly.ImportValue> {
|
|
308
|
+
const view = () => new DataView(getMemory());
|
|
309
|
+
const bytes = () => new Uint8Array(getMemory());
|
|
310
|
+
const fail = () => WASI_NOSYS;
|
|
311
|
+
return {
|
|
312
|
+
random_get: (ptr: number, len: number) => {
|
|
313
|
+
crypto.getRandomValues(bytes().subarray(ptr, ptr + len));
|
|
314
|
+
return 0;
|
|
315
|
+
},
|
|
316
|
+
clock_res_get: (_id: number, resPtr: number) => {
|
|
317
|
+
view().setBigUint64(resPtr, 1n, true);
|
|
318
|
+
return 0;
|
|
319
|
+
},
|
|
320
|
+
clock_time_get: (_id: number, _precision: bigint, timePtr: number) => {
|
|
321
|
+
view().setBigUint64(timePtr, BigInt(Date.now()) * 1000000n, true);
|
|
322
|
+
return 0;
|
|
323
|
+
},
|
|
324
|
+
fd_write: (fd: number, iovs: number, iovsLen: number, nwrittenPtr: number) => {
|
|
325
|
+
try {
|
|
326
|
+
const dataView = view();
|
|
327
|
+
let written = 0;
|
|
328
|
+
const chunks: Uint8Array[] = [];
|
|
329
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
330
|
+
const base = dataView.getUint32(iovs + i * 8, true);
|
|
331
|
+
const len = dataView.getUint32(iovs + i * 8 + 4, true);
|
|
332
|
+
chunks.push(bytes().slice(base, base + len));
|
|
333
|
+
written += len;
|
|
334
|
+
}
|
|
335
|
+
if (fd === 1 || fd === 2) {
|
|
336
|
+
const text = chunks.map((c) => new TextDecoder().decode(c)).join("");
|
|
337
|
+
if (isNode()) {
|
|
338
|
+
(fd === 1 ? process.stdout : process.stderr).write(text);
|
|
339
|
+
} else {
|
|
340
|
+
console.log(text);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
dataView.setUint32(nwrittenPtr, written, true);
|
|
344
|
+
return 0;
|
|
345
|
+
} catch {
|
|
346
|
+
return WASI_NOSYS;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
proc_exit: (code: number) => {
|
|
350
|
+
throw new Error(`galley-wasm: guest called proc_exit(${code})`);
|
|
351
|
+
},
|
|
352
|
+
// No preopened directories: BADF ends the preopen scan (NOSYS aborts libc init).
|
|
353
|
+
fd_prestat_get: () => WASI_BADF,
|
|
354
|
+
fd_fdstat_get: fail,
|
|
355
|
+
fd_filestat_get: fail,
|
|
356
|
+
fd_filestat_set_size: fail,
|
|
357
|
+
fd_filestat_set_times: fail,
|
|
358
|
+
fd_pread: fail,
|
|
359
|
+
fd_prestat_dir_name: fail,
|
|
360
|
+
fd_pwrite: fail,
|
|
361
|
+
fd_read: fail,
|
|
362
|
+
fd_seek: fail,
|
|
363
|
+
path_create_directory: fail,
|
|
364
|
+
path_filestat_get: fail,
|
|
365
|
+
path_filestat_set_times: fail,
|
|
366
|
+
path_link: fail,
|
|
367
|
+
path_open: fail,
|
|
368
|
+
path_readlink: fail,
|
|
369
|
+
path_remove_directory: fail,
|
|
370
|
+
path_rename: fail,
|
|
371
|
+
path_symlink: fail,
|
|
372
|
+
path_unlink_file: fail,
|
|
373
|
+
poll_oneoff: fail,
|
|
374
|
+
fd_sync: fail,
|
|
375
|
+
fd_readdir: fail,
|
|
376
|
+
fd_close: fail,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// --- loader -----------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
export interface InitOptions {
|
|
383
|
+
/** Grammar module path. Defaults to discovery (`findLibrary()` under Node). */
|
|
384
|
+
libraryPath?: string;
|
|
385
|
+
/** Raw module bytes (browsers, tests). Wins over `libraryPath`/`url`. */
|
|
386
|
+
bytes?: Uint8Array;
|
|
387
|
+
/** Module URL for `fetch` (browsers). Wins over `libraryPath`. */
|
|
388
|
+
url?: string | URL;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
interface PendingInstance {
|
|
392
|
+
port: WasmPort | null;
|
|
393
|
+
memory: ArrayBuffer | null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function makeImports(pending: PendingInstance): WebAssembly.Imports {
|
|
397
|
+
return {
|
|
398
|
+
wasi_snapshot_preview1: makeWasiStub(() => {
|
|
399
|
+
if (pending.memory === null) throw new Error("galley-wasm: memory unavailable");
|
|
400
|
+
return pending.memory;
|
|
401
|
+
}),
|
|
402
|
+
env: {
|
|
403
|
+
// Current builds import the ID entry; older modules import the
|
|
404
|
+
// name-carrying one. Both are always provided so either links.
|
|
405
|
+
galley_js_dispatch_id: (id: number, argsPtr: number) => {
|
|
406
|
+
const port = pending.port;
|
|
407
|
+
if (port === null) return;
|
|
408
|
+
port.dispatchFromGuestById(id, argsPtr);
|
|
409
|
+
},
|
|
410
|
+
galley_js_dispatch: (namePtr: number, nameLen: number, argsPtr: number) => {
|
|
411
|
+
const port = pending.port;
|
|
412
|
+
if (port === null) return;
|
|
413
|
+
port.dispatchFromGuest(namePtr, nameLen, argsPtr);
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function adoptInstance(
|
|
420
|
+
instance: WebAssembly.Instance,
|
|
421
|
+
wasmPath: string,
|
|
422
|
+
pending: PendingInstance,
|
|
423
|
+
): WasmPort {
|
|
424
|
+
pending.memory = (instance.exports.memory as WebAssembly.Memory).buffer;
|
|
425
|
+
if (typeof instance.exports._initialize === "function") {
|
|
426
|
+
(instance.exports._initialize as () => void)();
|
|
427
|
+
}
|
|
428
|
+
const port = new WasmPort(instance.exports as unknown as GalleyWasmExports, wasmPath);
|
|
429
|
+
pending.port = port;
|
|
430
|
+
ports.set(wasmPath, port);
|
|
431
|
+
return port;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function instantiate(bytes: Uint8Array<ArrayBuffer>, wasmPath: string): WasmPort {
|
|
435
|
+
const pending: PendingInstance = { port: null, memory: null };
|
|
436
|
+
const instance = new WebAssembly.Instance(new WebAssembly.Module(bytes), makeImports(pending));
|
|
437
|
+
return adoptInstance(instance, wasmPath, pending);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function loadBytesSync(options: InitOptions): { bytes: Uint8Array<ArrayBuffer>; wasmPath: string } {
|
|
441
|
+
if (options.bytes) {
|
|
442
|
+
return {
|
|
443
|
+
bytes: Uint8Array.from(options.bytes),
|
|
444
|
+
wasmPath: options.libraryPath ?? seededDefault ?? "<bytes>",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (options.url !== undefined) {
|
|
448
|
+
throw new NeedInitError(options.libraryPath);
|
|
449
|
+
}
|
|
450
|
+
if (!isNode()) throw new NeedInitError(options.libraryPath);
|
|
451
|
+
// findLibrary throws MissingArtifactError naming the exact place.
|
|
452
|
+
const wasmPath = options.libraryPath ?? seededDefault ?? findLibrary();
|
|
453
|
+
return { bytes: Uint8Array.from(new Uint8Array(fs.readFileSync(wasmPath))), wasmPath };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Async entry point; the only way to initialize in browsers. */
|
|
457
|
+
export async function init(options: InitOptions = {}): Promise<void> {
|
|
458
|
+
if (options.bytes) {
|
|
459
|
+
const wasmPath = options.libraryPath ?? seededDefault ?? "<bytes>";
|
|
460
|
+
instantiate(Uint8Array.from(options.bytes), wasmPath);
|
|
461
|
+
if (options.libraryPath === undefined) seededDefault = wasmPath;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (options.url !== undefined) {
|
|
465
|
+
const response = await fetch(options.url);
|
|
466
|
+
if (!response.ok) throw new Error(`galley-wasm: failed to fetch ${options.url}: ${response.status}`);
|
|
467
|
+
const wasmPath = options.libraryPath ?? seededDefault ?? String(options.url);
|
|
468
|
+
instantiate(new Uint8Array(await response.arrayBuffer()), wasmPath);
|
|
469
|
+
if (options.libraryPath === undefined) seededDefault = wasmPath;
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (!isNode()) throw new NeedInitError(options.libraryPath);
|
|
473
|
+
const { bytes, wasmPath } = loadBytesSync(options);
|
|
474
|
+
// Asynchronous compile for streaming-friendly startup; semantics match initSync.
|
|
475
|
+
const pending: PendingInstance = { port: null, memory: null };
|
|
476
|
+
const instance = await WebAssembly.instantiate(
|
|
477
|
+
await WebAssembly.compile(bytes),
|
|
478
|
+
makeImports(pending),
|
|
479
|
+
);
|
|
480
|
+
adoptInstance(instance, wasmPath, pending);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Synchronous entry point; Node only (file read + `WebAssembly.Module`). */
|
|
484
|
+
export function initSync(options: InitOptions = {}): void {
|
|
485
|
+
const { bytes, wasmPath } = loadBytesSync(options);
|
|
486
|
+
instantiate(bytes, wasmPath);
|
|
487
|
+
if (options.libraryPath === undefined) seededDefault = wasmPath;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Seed the default cache key (used by `init({ bytes })` without a path). */
|
|
491
|
+
export function seedDefault(wasmPath: string): void {
|
|
492
|
+
seededDefault = wasmPath;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function resolveKey(explicit?: string): string {
|
|
496
|
+
if (explicit) return explicit;
|
|
497
|
+
if (seededDefault !== null) return seededDefault;
|
|
498
|
+
if (!isNode()) throw new NeedInitError();
|
|
499
|
+
return findLibrary();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Single gate for every consumer: returns the initialized port for a
|
|
504
|
+
* grammar, auto-initializing synchronously under Node. Throws
|
|
505
|
+
* `NeedInitError` anywhere synchronous initialization is impossible.
|
|
506
|
+
*/
|
|
507
|
+
export function getWasmPort(libraryPath?: string): WasmPort {
|
|
508
|
+
const key = libraryPath ?? resolveKey();
|
|
509
|
+
const cached = ports.get(key);
|
|
510
|
+
if (cached) return cached;
|
|
511
|
+
if (!isNode()) throw new NeedInitError(libraryPath);
|
|
512
|
+
const resolved = libraryPath ?? findLibrary();
|
|
513
|
+
const direct = ports.get(resolved);
|
|
514
|
+
if (direct) return direct;
|
|
515
|
+
initSync(libraryPath ? { libraryPath: resolved } : {});
|
|
516
|
+
const port = ports.get(resolved) ?? ports.get(key);
|
|
517
|
+
if (!port) throw new NeedInitError(libraryPath);
|
|
518
|
+
return port;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// --- helpers ----------------------------------------------------------------
|
|
522
|
+
|
|
523
|
+
function isNegative(status: bigint): boolean {
|
|
524
|
+
return status < 0n;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function toNumber(value: bigint): number {
|
|
528
|
+
return Number(value);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Reinterpret a guest i64 as an unsigned u64 address (INVALID_NODE survives). */
|
|
532
|
+
function asAddress(value: bigint): bigint {
|
|
533
|
+
return BigInt.asUintN(64, value);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Encode a u64 address (possibly INVALID_NODE) as a guest i64. */
|
|
537
|
+
function asI64(value: bigint): bigint {
|
|
538
|
+
return BigInt.asIntN(64, value);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const textEncoder = new TextEncoder();
|
|
542
|
+
const textDecoder = new TextDecoder();
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* The wasm `FfiPort`: normalizes the reactor module's i32/i64 boundary
|
|
546
|
+
* into the structured values the core expects. Memory is allocated with
|
|
547
|
+
* the guest's `galley_js_malloc`/`galley_js_free`; every view is fresh
|
|
548
|
+
* because allocation may grow (and detach) memory.
|
|
549
|
+
*/
|
|
550
|
+
export class WasmPort implements FfiPort {
|
|
551
|
+
readonly wasm: GalleyWasmExports;
|
|
552
|
+
readonly libraryPath: string;
|
|
553
|
+
|
|
554
|
+
constructor(wasm: GalleyWasmExports, libraryPath: string) {
|
|
555
|
+
this.wasm = wasm;
|
|
556
|
+
this.libraryPath = libraryPath;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** Guest hook entry: decode the name and forward to the core registry. */
|
|
560
|
+
dispatchFromGuest(namePtr: number, nameLen: number, argsPtr: number): void {
|
|
561
|
+
let name: string;
|
|
562
|
+
try {
|
|
563
|
+
name = textDecoder.decode(this.readBytes(namePtr, nameLen));
|
|
564
|
+
} catch (error) {
|
|
565
|
+
console.error("galley procedure dispatch: failed to decode name", error);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
dispatchProcedure(name, argsPtr, this);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Guest hook entry (current builds): integer hook ID, no strings cross. */
|
|
572
|
+
dispatchFromGuestById(id: number, argsPtr: number): void {
|
|
573
|
+
const name = this.procedureNames()[id];
|
|
574
|
+
if (name === undefined) return;
|
|
575
|
+
dispatchProcedure(name, argsPtr, this);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
#procedureNameTable: string[] | null = null;
|
|
579
|
+
|
|
580
|
+
procedureNames(): string[] {
|
|
581
|
+
if (this.#procedureNameTable !== null) return this.#procedureNameTable;
|
|
582
|
+
const table: string[] = [];
|
|
583
|
+
if (
|
|
584
|
+
typeof this.wasm.galley_js_procedure_count === "function" &&
|
|
585
|
+
typeof this.wasm.galley_js_procedure_name_ptr === "function" &&
|
|
586
|
+
typeof this.wasm.galley_js_procedure_name_len === "function"
|
|
587
|
+
) {
|
|
588
|
+
const n = this.wasm.galley_js_procedure_count();
|
|
589
|
+
for (let i = 0; i < n; i++) {
|
|
590
|
+
const ptrValue = this.wasm.galley_js_procedure_name_ptr(i);
|
|
591
|
+
if (ptrValue === 0) break;
|
|
592
|
+
table.push(textDecoder.decode(this.readBytes(ptrValue, this.wasm.galley_js_procedure_name_len(i))));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
this.#procedureNameTable = table;
|
|
596
|
+
return table;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// -- memory -------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
private memoryBytes(): Uint8Array<ArrayBuffer> {
|
|
602
|
+
return new Uint8Array(this.wasm.memory.buffer);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private dataView(): DataView {
|
|
606
|
+
return new DataView(this.wasm.memory.buffer);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private malloc(len: number): number {
|
|
610
|
+
const ptr = this.wasm.galley_js_malloc(len);
|
|
611
|
+
if (ptr === 0) throw new Error("galley-wasm: out of memory");
|
|
612
|
+
return ptr;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private free(ptr: number, len: number): void {
|
|
616
|
+
if (len === 0) return;
|
|
617
|
+
this.wasm.galley_js_free(ptr, len);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Copy guest bytes out (owned copy, valid after the next call). */
|
|
621
|
+
private readBytes(ptr: number, len: number): Uint8Array<ArrayBuffer> {
|
|
622
|
+
if (ptr === 0 || len === 0) return new Uint8Array(0);
|
|
623
|
+
return this.memoryBytes().slice(ptr, ptr + len);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private readCString(ptr: number): string {
|
|
627
|
+
if (ptr === 0) return "";
|
|
628
|
+
const memory = this.memoryBytes();
|
|
629
|
+
let end = ptr;
|
|
630
|
+
while (memory[end] !== 0) end++;
|
|
631
|
+
return textDecoder.decode(memory.subarray(ptr, end));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Copy host bytes in; zero-length inputs still get a non-null slot. */
|
|
635
|
+
private writeBytes(data: Uint8Array): { ptr: number; len: number } {
|
|
636
|
+
const len = data.length;
|
|
637
|
+
const ptr = this.malloc(Math.max(len, 1));
|
|
638
|
+
if (len > 0) this.memoryBytes().set(data, ptr);
|
|
639
|
+
return { ptr, len };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// -- module-level queries -----------------------------------------------
|
|
643
|
+
|
|
644
|
+
version(): string {
|
|
645
|
+
return this.readCString(this.wasm.galley_version());
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
parserType(): number {
|
|
649
|
+
return toNumber(this.wasm.galley_parser_type());
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
errorRecoveryMode(): number {
|
|
653
|
+
return toNumber(this.wasm.galley_error_recovery_mode());
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
hasAst(): boolean {
|
|
657
|
+
return this.wasm.galley_has_ast() !== 0;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
hasProcedures(): boolean {
|
|
661
|
+
return this.wasm.galley_has_procedures() !== 0;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
allowsNoAstTreeProcedures(): boolean {
|
|
665
|
+
return this.wasm.galley_allows_no_ast_tree_procedures() !== 0;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
sourceRetentionEnabled(): boolean {
|
|
669
|
+
return this.wasm.galley_source_retention_enabled() !== 0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
hasPositionTracking(): boolean {
|
|
673
|
+
return this.wasm.galley_has_position_tracking() !== 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
hasInputStreaming(): boolean {
|
|
677
|
+
return this.wasm.galley_has_input_streaming() !== 0;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
usesVerbatim(): boolean {
|
|
681
|
+
return this.wasm.galley_uses_verbatim() !== 0;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
stackOverflowRecoveryAvailable(): boolean {
|
|
685
|
+
return this.wasm.galley_stack_overflow_recovery_available() !== 0;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
symbolCount(): number {
|
|
689
|
+
return toNumber(this.wasm.galley_symbol_count());
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
variableCount(): number {
|
|
693
|
+
return toNumber(this.wasm.galley_variable_count());
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
statusString(status: number): string | null {
|
|
697
|
+
const ptr = this.wasm.galley_status_string(BigInt(status));
|
|
698
|
+
if (ptr === 0) return null;
|
|
699
|
+
return this.readCString(ptr);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// -- sessions ------------------------------------------------------------
|
|
703
|
+
|
|
704
|
+
createSession(options: SessionCOptions | null): Handle {
|
|
705
|
+
if (options === null) {
|
|
706
|
+
const handle = this.wasm.galley_session_create();
|
|
707
|
+
if (handle === 0) return null;
|
|
708
|
+
return handle;
|
|
709
|
+
}
|
|
710
|
+
// GalleyCOptions layout (wasm32, little-endian): 5x i32/u32, pad, f64, u64.
|
|
711
|
+
const ptr = this.malloc(40);
|
|
712
|
+
try {
|
|
713
|
+
const view = this.dataView();
|
|
714
|
+
view.setInt32(ptr, options.maxErrors, true);
|
|
715
|
+
view.setInt32(ptr + 4, options.recoveryWindow, true);
|
|
716
|
+
view.setInt32(ptr + 8, options.stackOverflowRecovery, true);
|
|
717
|
+
view.setUint32(ptr + 12, options.syntaxErrorStackDepth, true);
|
|
718
|
+
view.setInt32(ptr + 16, options.verbosity, true);
|
|
719
|
+
view.setFloat64(ptr + 24, options.astPreallocationRatio, true);
|
|
720
|
+
view.setBigUint64(ptr + 32, options.astPreallocationCap, true);
|
|
721
|
+
const handle = this.wasm.galley_session_create_ex(ptr);
|
|
722
|
+
if (handle === 0) return null;
|
|
723
|
+
return handle;
|
|
724
|
+
} finally {
|
|
725
|
+
this.free(ptr, 40);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
destroySession(handle: Handle): void {
|
|
730
|
+
this.wasm.galley_session_destroy(handle as number);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
setMessageOverride(handle: Handle, name: Uint8Array, message: Uint8Array): number {
|
|
734
|
+
const nameBytes = textEncoder.encode(textDecoder.decode(name));
|
|
735
|
+
const messageBytes = textEncoder.encode(textDecoder.decode(message));
|
|
736
|
+
const nameSlot = this.writeBytes(nameBytes);
|
|
737
|
+
const messageSlot = this.writeBytes(messageBytes);
|
|
738
|
+
try {
|
|
739
|
+
return toNumber(
|
|
740
|
+
this.wasm.galley_session_set_message_override(
|
|
741
|
+
handle as number,
|
|
742
|
+
nameSlot.ptr,
|
|
743
|
+
nameSlot.len,
|
|
744
|
+
messageSlot.ptr,
|
|
745
|
+
messageSlot.len,
|
|
746
|
+
),
|
|
747
|
+
);
|
|
748
|
+
} finally {
|
|
749
|
+
this.free(nameSlot.ptr, Math.max(nameSlot.len, 1));
|
|
750
|
+
this.free(messageSlot.ptr, Math.max(messageSlot.len, 1));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// -- parsing --------------------------------------------------------------
|
|
755
|
+
|
|
756
|
+
parse(handle: Handle, data: Uint8Array): number {
|
|
757
|
+
const slot = this.writeBytes(data);
|
|
758
|
+
try {
|
|
759
|
+
return toNumber(this.wasm.galley_parse(handle as number, slot.ptr, slot.len));
|
|
760
|
+
} finally {
|
|
761
|
+
this.free(slot.ptr, Math.max(slot.len, 1));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
parseFile(handle: Handle, filePath: string): number {
|
|
766
|
+
// No guest filesystem: the host reads the file, then parses bytes.
|
|
767
|
+
// Mirrors the native `galley_error_io` (-11) contract on read failure.
|
|
768
|
+
let data: Uint8Array;
|
|
769
|
+
try {
|
|
770
|
+
data = new Uint8Array(fs.readFileSync(filePath));
|
|
771
|
+
} catch {
|
|
772
|
+
return -11;
|
|
773
|
+
}
|
|
774
|
+
return this.parse(handle, data);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
lastPosition(handle: Handle): [number, number] | null {
|
|
778
|
+
const out = this.malloc(8);
|
|
779
|
+
try {
|
|
780
|
+
const status = this.wasm.galley_last_position(handle as number, out, out + 4);
|
|
781
|
+
if (isNegative(status)) return null;
|
|
782
|
+
const view = this.dataView();
|
|
783
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
784
|
+
} finally {
|
|
785
|
+
this.free(out, 8);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// -- arena and navigation ---------------------------------------------------
|
|
790
|
+
|
|
791
|
+
nodeCount(handle: Handle): number {
|
|
792
|
+
return toNumber(this.wasm.galley_node_count(handle as number));
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
reserveNodes(handle: Handle, capacity: bigint): number {
|
|
796
|
+
return toNumber(this.wasm.galley_reserve_nodes(handle as number, asI64(capacity)));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
nodeCapacity(handle: Handle): number {
|
|
800
|
+
return toNumber(this.wasm.galley_node_capacity(handle as number));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
rootNode(handle: Handle): bigint {
|
|
804
|
+
return asAddress(this.wasm.galley_root_node(handle as number));
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
nodeValid(handle: Handle, node: bigint): boolean {
|
|
808
|
+
return this.wasm.galley_node_is_valid(handle as number, asI64(node)) !== 0;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
childCount(handle: Handle, node: bigint): number {
|
|
812
|
+
return this.wasm.galley_node_child_count(handle as number, asI64(node));
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
firstChild(handle: Handle, node: bigint): bigint {
|
|
816
|
+
return asAddress(this.wasm.galley_node_first_child(handle as number, asI64(node)));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
lastChild(handle: Handle, node: bigint): bigint {
|
|
820
|
+
return asAddress(this.wasm.galley_node_last_child(handle as number, asI64(node)));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
nextSibling(handle: Handle, node: bigint): bigint {
|
|
824
|
+
return asAddress(this.wasm.galley_node_next_sibling(handle as number, asI64(node)));
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
priorSibling(handle: Handle, node: bigint): bigint {
|
|
828
|
+
return asAddress(this.wasm.galley_node_prior_sibling(handle as number, asI64(node)));
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
parent(handle: Handle, node: bigint): bigint {
|
|
832
|
+
return asAddress(this.wasm.galley_node_parent(handle as number, asI64(node)));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
treeSnapshot(handle: Handle): TreeSnapshot {
|
|
836
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
837
|
+
const count = this.nodeCount(handle);
|
|
838
|
+
const empty = {
|
|
839
|
+
count,
|
|
840
|
+
parent: new BigUint64Array(0),
|
|
841
|
+
firstChild: new BigUint64Array(0),
|
|
842
|
+
next: new BigUint64Array(0),
|
|
843
|
+
childCount: new Uint32Array(0),
|
|
844
|
+
variable: new BigInt64Array(0),
|
|
845
|
+
spanStart: new BigUint64Array(0),
|
|
846
|
+
spanLen: new BigUint64Array(0),
|
|
847
|
+
};
|
|
848
|
+
if (count === 0) return empty;
|
|
849
|
+
// Eight-byte columns first (parent, firstChild, next, spanStart,
|
|
850
|
+
// spanLen, variable), then the u32 childCount tail: every column
|
|
851
|
+
// stays naturally aligned for bulk typed-array copies.
|
|
852
|
+
const stride = count * 8;
|
|
853
|
+
const offParent = 0;
|
|
854
|
+
const offFirst = stride;
|
|
855
|
+
const offNext = stride * 2;
|
|
856
|
+
const offSpanStart = stride * 3;
|
|
857
|
+
const offSpanLen = stride * 4;
|
|
858
|
+
const offVariable = stride * 5;
|
|
859
|
+
const offChildCount = stride * 6;
|
|
860
|
+
const total = offChildCount + count * 4;
|
|
861
|
+
const base = this.malloc(total);
|
|
862
|
+
try {
|
|
863
|
+
const status = this.wasm.galley_tree_snapshot(
|
|
864
|
+
handle as number, base + offParent, base + offFirst, base + offNext,
|
|
865
|
+
base + offChildCount, base + offVariable, base + offSpanStart,
|
|
866
|
+
base + offSpanLen, BigInt(count),
|
|
867
|
+
);
|
|
868
|
+
if (isNegative(status)) throw new GalleyError("galley_tree_snapshot failed", Number(status));
|
|
869
|
+
if (status !== BigInt(count)) continue;
|
|
870
|
+
const memory = this.memoryBytes();
|
|
871
|
+
const column64 = (offset: number) =>
|
|
872
|
+
new BigUint64Array(memory.buffer, memory.byteOffset + base + offset, count);
|
|
873
|
+
const parent = new BigUint64Array(count);
|
|
874
|
+
parent.set(column64(offParent));
|
|
875
|
+
const firstChild = new BigUint64Array(count);
|
|
876
|
+
firstChild.set(column64(offFirst));
|
|
877
|
+
const next = new BigUint64Array(count);
|
|
878
|
+
next.set(column64(offNext));
|
|
879
|
+
const spanStart = new BigUint64Array(count);
|
|
880
|
+
spanStart.set(column64(offSpanStart));
|
|
881
|
+
const spanLen = new BigUint64Array(count);
|
|
882
|
+
spanLen.set(column64(offSpanLen));
|
|
883
|
+
const variable = new BigInt64Array(count);
|
|
884
|
+
variable.set(new BigInt64Array(memory.buffer, memory.byteOffset + base + offVariable, count));
|
|
885
|
+
const childCount = new Uint32Array(count);
|
|
886
|
+
childCount.set(new Uint32Array(memory.buffer, memory.byteOffset + base + offChildCount, count));
|
|
887
|
+
return { count, parent, firstChild, next, childCount, variable, spanStart, spanLen };
|
|
888
|
+
} finally {
|
|
889
|
+
this.free(base, total);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
throw new GalleyError("node count changed during galley_tree_snapshot", -8);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// -- walker -------------------------------------------------------------------
|
|
896
|
+
|
|
897
|
+
walkerCreate(handle: Handle, node: bigint, skipSemanticErrors: boolean): Handle | null {
|
|
898
|
+
const walker = this.wasm.galley_walker_create(handle as number, asI64(node), skipSemanticErrors ? 1 : 0);
|
|
899
|
+
if (walker === 0) return null;
|
|
900
|
+
return walker;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
walkerNext(walker: Handle): WalkedStep | null {
|
|
904
|
+
const out = this.malloc(16);
|
|
905
|
+
try {
|
|
906
|
+
const yielded = this.wasm.galley_walker_next(walker as number, out, out + 8, out + 12);
|
|
907
|
+
if (yielded === 0) return null;
|
|
908
|
+
const view = this.dataView();
|
|
909
|
+
return {
|
|
910
|
+
node: view.getBigUint64(out, true),
|
|
911
|
+
depth: view.getUint32(out + 8, true),
|
|
912
|
+
isSemanticError: view.getInt32(out + 12, true) !== 0,
|
|
913
|
+
};
|
|
914
|
+
} finally {
|
|
915
|
+
this.free(out, 16);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
walkerSkipChildren(walker: Handle): void {
|
|
920
|
+
this.wasm.galley_walker_skip_children(walker as number);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
walkerDestroy(walker: Handle): void {
|
|
924
|
+
this.wasm.galley_walker_destroy(walker as number);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// -- node accessors ------------------------------------------------------------
|
|
928
|
+
|
|
929
|
+
/** Read a guest `(data, len)` byte pair; null on negative status. */
|
|
930
|
+
private tryCopyBytes(
|
|
931
|
+
call: (outData: number, outLen: number) => bigint,
|
|
932
|
+
): Uint8Array | null {
|
|
933
|
+
const out = this.malloc(8);
|
|
934
|
+
try {
|
|
935
|
+
const status = call(out, out + 4);
|
|
936
|
+
if (isNegative(status)) return null;
|
|
937
|
+
const view = this.dataView();
|
|
938
|
+
const ptr = view.getUint32(out, true);
|
|
939
|
+
const len = view.getUint32(out + 4, true);
|
|
940
|
+
if (ptr === 0) return null;
|
|
941
|
+
return this.readBytes(ptr, len);
|
|
942
|
+
} finally {
|
|
943
|
+
this.free(out, 8);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private readSemanticPair(
|
|
948
|
+
call: (
|
|
949
|
+
outVariable: number,
|
|
950
|
+
outVariableLen: number,
|
|
951
|
+
outMessage: number,
|
|
952
|
+
outMessageLen: number,
|
|
953
|
+
) => bigint,
|
|
954
|
+
): [string, string] | null {
|
|
955
|
+
const out = this.malloc(16);
|
|
956
|
+
try {
|
|
957
|
+
const status = call(out, out + 4, out + 8, out + 12);
|
|
958
|
+
if (isNegative(status)) return null;
|
|
959
|
+
const view = this.dataView();
|
|
960
|
+
const variablePtr = view.getUint32(out, true);
|
|
961
|
+
const variableLen = view.getUint32(out + 4, true);
|
|
962
|
+
const messagePtr = view.getUint32(out + 8, true);
|
|
963
|
+
const messageLen = view.getUint32(out + 12, true);
|
|
964
|
+
if (variablePtr === 0 || messagePtr === 0) return null;
|
|
965
|
+
return [
|
|
966
|
+
textDecoder.decode(this.readBytes(variablePtr, variableLen)),
|
|
967
|
+
textDecoder.decode(this.readBytes(messagePtr, messageLen)),
|
|
968
|
+
];
|
|
969
|
+
} finally {
|
|
970
|
+
this.free(out, 16);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
nodeSymbolName(handle: Handle, node: bigint): Uint8Array | null {
|
|
975
|
+
const session = handle as number;
|
|
976
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_node_symbol_name(session, asI64(node), data, len));
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
nodeText(handle: Handle, node: bigint): Uint8Array | null {
|
|
980
|
+
const session = handle as number;
|
|
981
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_node_text(session, asI64(node), data, len));
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
nodeSpan(handle: Handle, node: bigint): [bigint, bigint] | null {
|
|
985
|
+
const out = this.malloc(16);
|
|
986
|
+
try {
|
|
987
|
+
const status = this.wasm.galley_node_span(handle as number, asI64(node), out, out + 8);
|
|
988
|
+
if (isNegative(status)) return null;
|
|
989
|
+
const view = this.dataView();
|
|
990
|
+
return [view.getBigUint64(out, true), view.getBigUint64(out + 8, true)];
|
|
991
|
+
} finally {
|
|
992
|
+
this.free(out, 16);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
nodeLineColumn(handle: Handle, node: bigint): [number, number] | null {
|
|
997
|
+
const out = this.malloc(8);
|
|
998
|
+
try {
|
|
999
|
+
const status = this.wasm.galley_node_line_column(handle as number, asI64(node), out, out + 4);
|
|
1000
|
+
if (isNegative(status)) return null;
|
|
1001
|
+
const view = this.dataView();
|
|
1002
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
1003
|
+
} finally {
|
|
1004
|
+
this.free(out, 8);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
nodeVariableIndex(handle: Handle, node: bigint): number {
|
|
1009
|
+
return toNumber(this.wasm.galley_node_variable_index(handle as number, asI64(node)));
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
symbolNameAt(handle: Handle, index: number): Uint8Array | null {
|
|
1013
|
+
const session = handle as number;
|
|
1014
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_symbol_name(session, BigInt(index), data, len));
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
symbolIsTerminal(handle: Handle, index: number): boolean {
|
|
1018
|
+
return this.wasm.galley_symbol_is_terminal(handle as number, BigInt(index)) !== 0;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
variableNameAt(handle: Handle, index: number): Uint8Array | null {
|
|
1022
|
+
const session = handle as number;
|
|
1023
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_variable_name(session, BigInt(index), data, len));
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// -- diagnostics ------------------------------------------------------------------
|
|
1027
|
+
|
|
1028
|
+
hasDiagnostic(handle: Handle): boolean {
|
|
1029
|
+
return this.wasm.galley_has_diagnostic(handle as number) !== 0;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
diagnosticKind(handle: Handle): number {
|
|
1033
|
+
return toNumber(this.wasm.galley_diagnostic_kind(handle as number));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
diagnosticMessage(handle: Handle): string | null {
|
|
1037
|
+
const out = this.malloc(4);
|
|
1038
|
+
try {
|
|
1039
|
+
if (this.wasm.galley_diagnostic_message(handle as number, out) !== 0n) return null;
|
|
1040
|
+
return this.readCString(this.dataView().getUint32(out, true));
|
|
1041
|
+
} finally {
|
|
1042
|
+
this.free(out, 4);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
diagnosticMessageAnsi(handle: Handle): string | null {
|
|
1047
|
+
const out = this.malloc(4);
|
|
1048
|
+
try {
|
|
1049
|
+
if (this.wasm.galley_diagnostic_message_ansi(handle as number, out) !== 0n) return null;
|
|
1050
|
+
return this.readCString(this.dataView().getUint32(out, true));
|
|
1051
|
+
} finally {
|
|
1052
|
+
this.free(out, 4);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
diagnosticPosition(handle: Handle): [number, number] | null {
|
|
1057
|
+
const out = this.malloc(8);
|
|
1058
|
+
try {
|
|
1059
|
+
if (isNegative(this.wasm.galley_diagnostic_position(handle as number, out, out + 4)))
|
|
1060
|
+
return null;
|
|
1061
|
+
const view = this.dataView();
|
|
1062
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
1063
|
+
} finally {
|
|
1064
|
+
this.free(out, 8);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
diagnosticUnexpectedToken(handle: Handle): Uint8Array | null {
|
|
1069
|
+
const session = handle as number;
|
|
1070
|
+
return this.tryCopyBytes((data, len) => this.wasm.galley_diagnostic_unexpected_token(session, data, len));
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
diagnosticExpectedCount(handle: Handle): number {
|
|
1074
|
+
return toNumber(this.wasm.galley_diagnostic_expected_count(handle as number));
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
diagnosticExpectedAt(handle: Handle, index: number): Uint8Array | null {
|
|
1078
|
+
const session = handle as number;
|
|
1079
|
+
return this.tryCopyBytes((data, len) =>
|
|
1080
|
+
this.wasm.galley_diagnostic_expected_at(session, BigInt(index), data, len),
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
diagnosticContextCount(handle: Handle): number {
|
|
1085
|
+
return toNumber(this.wasm.galley_diagnostic_context_count(handle as number));
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
diagnosticContextAt(handle: Handle, index: number): Uint8Array | null {
|
|
1089
|
+
const session = handle as number;
|
|
1090
|
+
return this.tryCopyBytes((data, len) =>
|
|
1091
|
+
this.wasm.galley_diagnostic_context_at(session, BigInt(index), data, len),
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
syntaxErrorCount(handle: Handle): number {
|
|
1096
|
+
return toNumber(this.wasm.galley_syntax_error_count(handle as number));
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
semanticErrorCount(handle: Handle): number {
|
|
1100
|
+
return toNumber(this.wasm.galley_semantic_error_count(handle as number));
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
diagnosticSemantic(handle: Handle): [string, string] | null {
|
|
1104
|
+
const session = handle as number;
|
|
1105
|
+
return this.readSemanticPair((variable, variableLen, message, messageLen) =>
|
|
1106
|
+
this.wasm.galley_diagnostic_semantic(session, variable, variableLen, message, messageLen),
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
diagnosticIndentation(handle: Handle): [number, number] | null {
|
|
1111
|
+
const out = this.malloc(8);
|
|
1112
|
+
try {
|
|
1113
|
+
if (toNumber(this.wasm.galley_diagnostic_indentation(handle as number, out, out + 4)) !== 0)
|
|
1114
|
+
return null;
|
|
1115
|
+
const view = this.dataView();
|
|
1116
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
1117
|
+
} finally {
|
|
1118
|
+
this.free(out, 8);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
diagnosticRecoveryKind(handle: Handle): number {
|
|
1123
|
+
return toNumber(this.wasm.galley_diagnostic_recovery_kind(handle as number));
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
diagnosticRecoveryTerminal(handle: Handle): Uint8Array | null {
|
|
1127
|
+
const session = handle as number;
|
|
1128
|
+
return this.tryCopyBytes((data, len) =>
|
|
1129
|
+
this.wasm.galley_diagnostic_recovery_terminal(session, data, len),
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
diagnosticRecoveryResume(handle: Handle): number | null {
|
|
1134
|
+
const out = this.malloc(8);
|
|
1135
|
+
try {
|
|
1136
|
+
if (toNumber(this.wasm.galley_diagnostic_recovery_resume(handle as number, out)) !== 0)
|
|
1137
|
+
return null;
|
|
1138
|
+
return toNumber(this.dataView().getBigInt64(out, true));
|
|
1139
|
+
} finally {
|
|
1140
|
+
this.free(out, 8);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
diagnosticRecoveryLhsVariable(handle: Handle): string | null {
|
|
1145
|
+
const session = handle as number;
|
|
1146
|
+
const out = this.malloc(8);
|
|
1147
|
+
try {
|
|
1148
|
+
const status = this.wasm.galley_diagnostic_recovery_lhs_variable(session, out, out + 4);
|
|
1149
|
+
if (isNegative(status)) return null;
|
|
1150
|
+
const view = this.dataView();
|
|
1151
|
+
const ptr = view.getUint32(out, true);
|
|
1152
|
+
if (ptr === 0) return null;
|
|
1153
|
+
return textDecoder.decode(this.readBytes(ptr, view.getUint32(out + 4, true)));
|
|
1154
|
+
} finally {
|
|
1155
|
+
this.free(out, 8);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
diagnosticRecoveryProduction(handle: Handle): [string, number] | null {
|
|
1160
|
+
const out = this.malloc(12);
|
|
1161
|
+
try {
|
|
1162
|
+
if (
|
|
1163
|
+
toNumber(this.wasm.galley_diagnostic_recovery_production(handle as number, out, out + 4, out + 8)) !==
|
|
1164
|
+
0
|
|
1165
|
+
)
|
|
1166
|
+
return null;
|
|
1167
|
+
const view = this.dataView();
|
|
1168
|
+
return [
|
|
1169
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
1170
|
+
view.getUint32(out + 8, true),
|
|
1171
|
+
];
|
|
1172
|
+
} finally {
|
|
1173
|
+
this.free(out, 12);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
diagnosticRecoveryOccurrence(handle: Handle): [string, number, number, string] | null {
|
|
1178
|
+
const out = this.malloc(24);
|
|
1179
|
+
try {
|
|
1180
|
+
if (
|
|
1181
|
+
toNumber(
|
|
1182
|
+
this.wasm.galley_diagnostic_recovery_occurrence(
|
|
1183
|
+
handle as number,
|
|
1184
|
+
out,
|
|
1185
|
+
out + 4,
|
|
1186
|
+
out + 8,
|
|
1187
|
+
out + 12,
|
|
1188
|
+
out + 16,
|
|
1189
|
+
out + 20,
|
|
1190
|
+
),
|
|
1191
|
+
) !== 0
|
|
1192
|
+
)
|
|
1193
|
+
return null;
|
|
1194
|
+
const view = this.dataView();
|
|
1195
|
+
return [
|
|
1196
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
1197
|
+
view.getUint32(out + 8, true),
|
|
1198
|
+
view.getUint32(out + 12, true),
|
|
1199
|
+
textDecoder.decode(this.readBytes(view.getUint32(out + 16, true), view.getUint32(out + 20, true))),
|
|
1200
|
+
];
|
|
1201
|
+
} finally {
|
|
1202
|
+
this.free(out, 24);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
recordedDiagnosticCount(handle: Handle): number {
|
|
1207
|
+
return toNumber(this.wasm.galley_recorded_diagnostic_count(handle as number));
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
recordedDiagnosticKind(handle: Handle, diagIndex: number): number {
|
|
1211
|
+
return toNumber(this.wasm.galley_recorded_diagnostic_kind(handle as number, BigInt(diagIndex)));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
recordedDiagnosticPosition(handle: Handle, diagIndex: number): [number, number] | null {
|
|
1215
|
+
const out = this.malloc(8);
|
|
1216
|
+
try {
|
|
1217
|
+
if (
|
|
1218
|
+
isNegative(this.wasm.galley_recorded_diagnostic_position(handle as number, BigInt(diagIndex), out, out + 4))
|
|
1219
|
+
)
|
|
1220
|
+
return null;
|
|
1221
|
+
const view = this.dataView();
|
|
1222
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
1223
|
+
} finally {
|
|
1224
|
+
this.free(out, 8);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
recordedUnexpectedToken(handle: Handle, diagIndex: number): Uint8Array | null {
|
|
1229
|
+
const session = handle as number;
|
|
1230
|
+
return this.tryCopyBytes((data, len) =>
|
|
1231
|
+
this.wasm.galley_recorded_unexpected_token(session, BigInt(diagIndex), data, len),
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
recordedDiagnosticMessage(handle: Handle, diagIndex: number): string | null {
|
|
1236
|
+
const out = this.malloc(4);
|
|
1237
|
+
try {
|
|
1238
|
+
if (toNumber(this.wasm.galley_recorded_diagnostic_message(handle as number, BigInt(diagIndex), out)) !== 0)
|
|
1239
|
+
return null;
|
|
1240
|
+
return this.readCString(this.dataView().getUint32(out, true));
|
|
1241
|
+
} finally {
|
|
1242
|
+
this.free(out, 4);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
recordedIndentation(handle: Handle, diagIndex: number): [number, number] | null {
|
|
1247
|
+
const out = this.malloc(8);
|
|
1248
|
+
try {
|
|
1249
|
+
if (
|
|
1250
|
+
toNumber(this.wasm.galley_recorded_indentation(handle as number, BigInt(diagIndex), out, out + 4)) !== 0
|
|
1251
|
+
)
|
|
1252
|
+
return null;
|
|
1253
|
+
const view = this.dataView();
|
|
1254
|
+
return [view.getUint32(out, true), view.getUint32(out + 4, true)];
|
|
1255
|
+
} finally {
|
|
1256
|
+
this.free(out, 8);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
recordedSemantic(handle: Handle, diagIndex: number): [string, string] | null {
|
|
1261
|
+
const session = handle as number;
|
|
1262
|
+
return this.readSemanticPair((variable, variableLen, message, messageLen) =>
|
|
1263
|
+
this.wasm.galley_recorded_semantic(session, BigInt(diagIndex), variable, variableLen, message, messageLen),
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
recordedExpectedCount(handle: Handle, diagIndex: number): number {
|
|
1268
|
+
return toNumber(this.wasm.galley_recorded_expected_count(handle as number, BigInt(diagIndex)));
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
recordedExpectedToken(handle: Handle, diagIndex: number, tokenIndex: number): Uint8Array | null {
|
|
1272
|
+
const session = handle as number;
|
|
1273
|
+
return this.tryCopyBytes((data, len) =>
|
|
1274
|
+
this.wasm.galley_recorded_expected_token(session, BigInt(diagIndex), BigInt(tokenIndex), data, len),
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
recordedContextCount(handle: Handle, diagIndex: number): number {
|
|
1279
|
+
return toNumber(this.wasm.galley_recorded_context_count(handle as number, BigInt(diagIndex)));
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
recordedContextName(handle: Handle, diagIndex: number, contextIndex: number): Uint8Array | null {
|
|
1283
|
+
const session = handle as number;
|
|
1284
|
+
return this.tryCopyBytes((data, len) =>
|
|
1285
|
+
this.wasm.galley_recorded_context_name(session, BigInt(diagIndex), BigInt(contextIndex), data, len),
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
recordedRecoveryKind(handle: Handle, diagIndex: number): number {
|
|
1290
|
+
return toNumber(this.wasm.galley_recorded_diagnostic_recovery_kind(handle as number, BigInt(diagIndex)));
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
recordedRecoveryTerminal(handle: Handle, diagIndex: number): Uint8Array | null {
|
|
1294
|
+
const session = handle as number;
|
|
1295
|
+
return this.tryCopyBytes((data, len) =>
|
|
1296
|
+
this.wasm.galley_recorded_recovery_terminal(session, BigInt(diagIndex), data, len),
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
recordedRecoveryResume(handle: Handle, diagIndex: number): number | null {
|
|
1301
|
+
const out = this.malloc(8);
|
|
1302
|
+
try {
|
|
1303
|
+
if (toNumber(this.wasm.galley_recorded_recovery_resume(handle as number, BigInt(diagIndex), out)) !== 0)
|
|
1304
|
+
return null;
|
|
1305
|
+
return toNumber(this.dataView().getBigInt64(out, true));
|
|
1306
|
+
} finally {
|
|
1307
|
+
this.free(out, 8);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
recordedRecoveryLhsVariable(handle: Handle, diagIndex: number): string | null {
|
|
1312
|
+
const session = handle as number;
|
|
1313
|
+
const out = this.malloc(8);
|
|
1314
|
+
try {
|
|
1315
|
+
const status = this.wasm.galley_recorded_recovery_lhs_variable(session, BigInt(diagIndex), out, out + 4);
|
|
1316
|
+
if (isNegative(status)) return null;
|
|
1317
|
+
const view = this.dataView();
|
|
1318
|
+
const ptr = view.getUint32(out, true);
|
|
1319
|
+
if (ptr === 0) return null;
|
|
1320
|
+
return textDecoder.decode(this.readBytes(ptr, view.getUint32(out + 4, true)));
|
|
1321
|
+
} finally {
|
|
1322
|
+
this.free(out, 8);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
recordedRecoveryProduction(handle: Handle, diagIndex: number): [string, number] | null {
|
|
1327
|
+
const out = this.malloc(12);
|
|
1328
|
+
try {
|
|
1329
|
+
if (
|
|
1330
|
+
toNumber(
|
|
1331
|
+
this.wasm.galley_recorded_recovery_production(handle as number, BigInt(diagIndex), out, out + 4, out + 8),
|
|
1332
|
+
) !== 0
|
|
1333
|
+
)
|
|
1334
|
+
return null;
|
|
1335
|
+
const view = this.dataView();
|
|
1336
|
+
return [
|
|
1337
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
1338
|
+
view.getUint32(out + 8, true),
|
|
1339
|
+
];
|
|
1340
|
+
} finally {
|
|
1341
|
+
this.free(out, 12);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
recordedRecoveryOccurrence(
|
|
1346
|
+
handle: Handle,
|
|
1347
|
+
diagIndex: number,
|
|
1348
|
+
): [string, number, number, string] | null {
|
|
1349
|
+
const out = this.malloc(24);
|
|
1350
|
+
try {
|
|
1351
|
+
if (
|
|
1352
|
+
toNumber(
|
|
1353
|
+
this.wasm.galley_recorded_recovery_occurrence(
|
|
1354
|
+
handle as number,
|
|
1355
|
+
BigInt(diagIndex),
|
|
1356
|
+
out,
|
|
1357
|
+
out + 4,
|
|
1358
|
+
out + 8,
|
|
1359
|
+
out + 12,
|
|
1360
|
+
out + 16,
|
|
1361
|
+
out + 20,
|
|
1362
|
+
),
|
|
1363
|
+
) !== 0
|
|
1364
|
+
)
|
|
1365
|
+
return null;
|
|
1366
|
+
const view = this.dataView();
|
|
1367
|
+
return [
|
|
1368
|
+
textDecoder.decode(this.readBytes(view.getUint32(out, true), view.getUint32(out + 4, true))),
|
|
1369
|
+
view.getUint32(out + 8, true),
|
|
1370
|
+
view.getUint32(out + 12, true),
|
|
1371
|
+
textDecoder.decode(this.readBytes(view.getUint32(out + 16, true), view.getUint32(out + 20, true))),
|
|
1372
|
+
];
|
|
1373
|
+
} finally {
|
|
1374
|
+
this.free(out, 24);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// -- tree editing --------------------------------------------------------------------
|
|
1379
|
+
|
|
1380
|
+
treeAppendChildren(handle: Handle, parent: bigint, first: bigint): number {
|
|
1381
|
+
return toNumber(this.wasm.galley_tree_append_children(handle as number, asI64(parent), asI64(first)));
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
treeInsertBefore(handle: Handle, target: bigint, first: bigint): number {
|
|
1385
|
+
return toNumber(this.wasm.galley_tree_insert_before(handle as number, asI64(target), asI64(first)));
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
treeInsertAfter(handle: Handle, target: bigint, first: bigint): number {
|
|
1389
|
+
return toNumber(this.wasm.galley_tree_insert_after(handle as number, asI64(target), asI64(first)));
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
treeRemoveSiblings(handle: Handle, node: bigint, count: number): { status: number; head: bigint } {
|
|
1393
|
+
const out = this.malloc(8);
|
|
1394
|
+
try {
|
|
1395
|
+
const status = toNumber(
|
|
1396
|
+
this.wasm.galley_tree_remove_siblings(handle as number, asI64(node), count, out),
|
|
1397
|
+
);
|
|
1398
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1399
|
+
} finally {
|
|
1400
|
+
this.free(out, 8);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
treeRemoveSelf(handle: Handle, node: bigint): { status: number; head: bigint } {
|
|
1405
|
+
const out = this.malloc(8);
|
|
1406
|
+
try {
|
|
1407
|
+
const status = toNumber(this.wasm.galley_tree_remove_self(handle as number, asI64(node), out));
|
|
1408
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1409
|
+
} finally {
|
|
1410
|
+
this.free(out, 8);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
treePromoteChildrenOverWrapper(handle: Handle, wrapper: bigint): { status: number; head: bigint } {
|
|
1415
|
+
const out = this.malloc(8);
|
|
1416
|
+
try {
|
|
1417
|
+
const status = toNumber(
|
|
1418
|
+
this.wasm.galley_tree_promote_children_over_wrapper(handle as number, asI64(wrapper), out),
|
|
1419
|
+
);
|
|
1420
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1421
|
+
} finally {
|
|
1422
|
+
this.free(out, 8);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
treeCleanChildren(handle: Handle, node: bigint): { status: number; head: bigint } {
|
|
1427
|
+
const out = this.malloc(8);
|
|
1428
|
+
try {
|
|
1429
|
+
const status = toNumber(this.wasm.galley_tree_clean_children(handle as number, asI64(node), out));
|
|
1430
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1431
|
+
} finally {
|
|
1432
|
+
this.free(out, 8);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
treeUnlinkWrapper(handle: Handle, wrapper: bigint): number {
|
|
1437
|
+
return toNumber(this.wasm.galley_tree_unlink_wrapper(handle as number, asI64(wrapper)));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
treeInsertChildrenAt(handle: Handle, parent: bigint, index: number, first: bigint): number {
|
|
1441
|
+
return toNumber(
|
|
1442
|
+
this.wasm.galley_tree_insert_children_at(handle as number, asI64(parent), index, asI64(first)),
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
treeRemoveChildrenAt(
|
|
1447
|
+
handle: Handle,
|
|
1448
|
+
parent: bigint,
|
|
1449
|
+
index: number,
|
|
1450
|
+
count: number,
|
|
1451
|
+
): { status: number; head: bigint } {
|
|
1452
|
+
const out = this.malloc(8);
|
|
1453
|
+
try {
|
|
1454
|
+
const status = toNumber(
|
|
1455
|
+
this.wasm.galley_tree_remove_children_at(handle as number, asI64(parent), index, count, out),
|
|
1456
|
+
);
|
|
1457
|
+
return { status, head: this.dataView().getBigUint64(out, true) };
|
|
1458
|
+
} finally {
|
|
1459
|
+
this.free(out, 8);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// -- procedure hooks (parse-time state) ---------------------------------------------------
|
|
1464
|
+
|
|
1465
|
+
procCurrentNode(args: Handle): bigint {
|
|
1466
|
+
return asAddress(this.wasm.galley_procedure_current_node(args as number));
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
procSetCurrentNode(args: Handle, node: bigint): void {
|
|
1470
|
+
this.wasm.galley_procedure_set_current_node(args as number, asI64(node));
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
procDropSelf(args: Handle): number {
|
|
1474
|
+
return toNumber(this.wasm.galley_procedure_drop_self(args as number));
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
procDropChildren(args: Handle): number {
|
|
1478
|
+
return toNumber(this.wasm.galley_procedure_drop_children(args as number));
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
procDropIfEmpty(args: Handle): number {
|
|
1482
|
+
return toNumber(this.wasm.galley_procedure_drop_if_empty(args as number));
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
procReplaceWithChildren(args: Handle): number {
|
|
1486
|
+
return toNumber(this.wasm.galley_procedure_replace_with_children(args as number));
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
procContextLine(args: Handle): number {
|
|
1490
|
+
return this.wasm.galley_procedure_context_line(args as number);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
procContextColumn(args: Handle): number {
|
|
1494
|
+
return this.wasm.galley_procedure_context_column(args as number);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
procReportSemanticError(args: Handle, message: Uint8Array): number {
|
|
1498
|
+
const bytes = textEncoder.encode(textDecoder.decode(message));
|
|
1499
|
+
const slot = this.writeBytes(bytes);
|
|
1500
|
+
try {
|
|
1501
|
+
return toNumber(
|
|
1502
|
+
this.wasm.galley_procedure_report_semantic_error(args as number, slot.ptr, slot.len),
|
|
1503
|
+
);
|
|
1504
|
+
} finally {
|
|
1505
|
+
this.free(slot.ptr, Math.max(slot.len, 1));
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
syncProcedures(names: string[]): void {
|
|
1510
|
+
if (typeof this.wasm.galley_js_procedure_clear !== "function") return;
|
|
1511
|
+
if (typeof this.wasm.galley_js_procedure_enable !== "function") return;
|
|
1512
|
+
this.wasm.galley_js_procedure_clear();
|
|
1513
|
+
for (const name of names) {
|
|
1514
|
+
const bytes = textEncoder.encode(name);
|
|
1515
|
+
const slot = this.writeBytes(bytes);
|
|
1516
|
+
try {
|
|
1517
|
+
this.wasm.galley_js_procedure_enable(slot.ptr, slot.len);
|
|
1518
|
+
} finally {
|
|
1519
|
+
this.free(slot.ptr, Math.max(slot.len, 1));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
// Warm the ID table outside any parse so the hot path never queries
|
|
1523
|
+
// (querying would re-enter the guest mid-parse).
|
|
1524
|
+
this.procedureNames();
|
|
1525
|
+
}
|
|
1526
|
+
}
|