@sanbus/galley-deno 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/src/ffi.ts ADDED
@@ -0,0 +1,1032 @@
1
+ /**
2
+ * Deno adapter for the Galley JavaScript bindings: `Deno.dlopen` bindings
3
+ * over `bindings/c/galley.h`, implementing the core `FfiPort`.
4
+ *
5
+ * Zero dependencies: no npm packages, no build step for the adapter itself.
6
+ * The core (`@sanbus/galley-core`, resolved to its compiled `dist` via the
7
+ * package `deno.json` import map) owns all session logic; memory copying
8
+ * and integer normalization live here. Requires `--allow-ffi` (dlopen),
9
+ * `--allow-read` (library discovery, `parseFile`), and `--allow-env`
10
+ * (library discovery).
11
+ */
12
+
13
+ import type { FfiPort, Handle, SessionCOptions, TreeSnapshot, WalkedStep } from "@sanbus/galley-core";
14
+ import { GalleyError, resolveArtifact, artifactFileName } from "@sanbus/galley-core";
15
+
16
+ const textEncoder = new TextEncoder();
17
+ const textDecoder = new TextDecoder();
18
+
19
+ /** Callable view of the native symbols (see `BASE_SYMBOLS` below). */
20
+ type FfiOut = Uint8Array | Uint32Array | BigUint64Array | BigInt64Array;
21
+
22
+ interface GalleySymbols {
23
+ galley_version(): Deno.PointerValue;
24
+ galley_parser_type(): bigint;
25
+ galley_error_recovery_mode(): bigint;
26
+ galley_has_ast(): number;
27
+ galley_has_procedures(): number;
28
+ galley_allows_no_ast_tree_procedures(): number;
29
+ galley_source_retention_enabled(): number;
30
+ galley_has_position_tracking(): number;
31
+ galley_has_input_streaming(): number;
32
+ galley_uses_verbatim(): number;
33
+ galley_stack_overflow_recovery_available(): number;
34
+ galley_symbol_count(): bigint;
35
+ galley_variable_count(): bigint;
36
+ galley_status_string(status: bigint): Deno.PointerValue;
37
+ galley_symbol_name(session: Deno.PointerValue, index: bigint, outData: FfiOut, outLen: FfiOut): bigint;
38
+ galley_symbol_is_terminal(session: Deno.PointerValue, index: bigint): number;
39
+ galley_variable_name(session: Deno.PointerValue, index: bigint, outData: FfiOut, outLen: FfiOut): bigint;
40
+ galley_session_create(): Deno.PointerValue;
41
+ galley_session_create_ex(options: FfiOut): Deno.PointerValue;
42
+ galley_session_destroy(session: Deno.PointerValue): void;
43
+ galley_session_set_message_override(session: Deno.PointerValue, name: FfiOut, nameLen: number, message: FfiOut, messageLen: number): bigint;
44
+ galley_parse(session: Deno.PointerValue, data: FfiOut, len: number): bigint;
45
+ galley_parse_file(session: Deno.PointerValue, path: FfiOut): bigint;
46
+ galley_last_position(session: Deno.PointerValue, outLine: FfiOut, outCol: FfiOut): bigint;
47
+ galley_node_count(session: Deno.PointerValue): bigint;
48
+ galley_reserve_nodes(session: Deno.PointerValue, capacity: bigint): bigint;
49
+ galley_node_capacity(session: Deno.PointerValue): bigint;
50
+ galley_root_node(session: Deno.PointerValue): bigint;
51
+ galley_node_is_valid(session: Deno.PointerValue, node: bigint): number;
52
+ galley_node_child_count(session: Deno.PointerValue, node: bigint): number;
53
+ galley_node_first_child(session: Deno.PointerValue, node: bigint): bigint;
54
+ galley_node_last_child(session: Deno.PointerValue, node: bigint): bigint;
55
+ galley_node_next_sibling(session: Deno.PointerValue, node: bigint): bigint;
56
+ galley_node_prior_sibling(session: Deno.PointerValue, node: bigint): bigint;
57
+ galley_node_parent(session: Deno.PointerValue, node: bigint): bigint;
58
+ galley_tree_snapshot(
59
+ session: Deno.PointerValue,
60
+ outParent: FfiOut,
61
+ outFirstChild: FfiOut,
62
+ outNext: FfiOut,
63
+ outChildCount: FfiOut,
64
+ outVariable: FfiOut,
65
+ outSpanStart: FfiOut,
66
+ outSpanLen: FfiOut,
67
+ capacity: bigint,
68
+ ): bigint;
69
+ galley_walker_create(session: Deno.PointerValue, node: bigint, skipSemanticErrors: number): Deno.PointerValue;
70
+ galley_walker_next(walker: Deno.PointerValue, outNode: FfiOut, outDepth: FfiOut, outFlag: FfiOut): number;
71
+ galley_walker_skip_children(walker: Deno.PointerValue): void;
72
+ galley_walker_destroy(walker: Deno.PointerValue): void;
73
+ galley_node_span(session: Deno.PointerValue, node: bigint, outStart: FfiOut, outLen: FfiOut): bigint;
74
+ galley_node_symbol_name(session: Deno.PointerValue, node: bigint, outData: FfiOut, outLen: FfiOut): bigint;
75
+ galley_node_variable_index(session: Deno.PointerValue, node: bigint): bigint;
76
+ galley_node_text(session: Deno.PointerValue, node: bigint, outData: FfiOut, outLen: FfiOut): bigint;
77
+ galley_node_line_column(session: Deno.PointerValue, node: bigint, outLine: FfiOut, outCol: FfiOut): bigint;
78
+ galley_has_diagnostic(session: Deno.PointerValue): number;
79
+ galley_diagnostic_kind(session: Deno.PointerValue): bigint;
80
+ galley_diagnostic_message(session: Deno.PointerValue, out: FfiOut): bigint;
81
+ galley_diagnostic_message_ansi(session: Deno.PointerValue, out: FfiOut): bigint;
82
+ galley_diagnostic_position(session: Deno.PointerValue, outLine: FfiOut, outCol: FfiOut): bigint;
83
+ galley_diagnostic_unexpected_token(session: Deno.PointerValue, outData: FfiOut, outLen: FfiOut): bigint;
84
+ galley_diagnostic_expected_count(session: Deno.PointerValue): bigint;
85
+ galley_diagnostic_expected_at(session: Deno.PointerValue, index: bigint, outData: FfiOut, outLen: FfiOut): bigint;
86
+ galley_diagnostic_context_count(session: Deno.PointerValue): bigint;
87
+ galley_diagnostic_context_at(session: Deno.PointerValue, index: bigint, outData: FfiOut, outLen: FfiOut): bigint;
88
+ galley_diagnostic_indentation(session: Deno.PointerValue, outSpaces: FfiOut, outWidth: FfiOut): bigint;
89
+ galley_syntax_error_count(session: Deno.PointerValue): bigint;
90
+ galley_semantic_error_count(session: Deno.PointerValue): bigint;
91
+ galley_diagnostic_semantic(session: Deno.PointerValue, outVariable: FfiOut, outVariableLen: FfiOut, outMessage: FfiOut, outMessageLen: FfiOut): bigint;
92
+ galley_diagnostic_recovery_kind(session: Deno.PointerValue): bigint;
93
+ galley_diagnostic_recovery_terminal(session: Deno.PointerValue, outData: FfiOut, outLen: FfiOut): bigint;
94
+ galley_diagnostic_recovery_resume(session: Deno.PointerValue, out: FfiOut): bigint;
95
+ galley_diagnostic_recovery_lhs_variable(session: Deno.PointerValue, outData: FfiOut, outLen: FfiOut): bigint;
96
+ galley_diagnostic_recovery_production(session: Deno.PointerValue, outVar: FfiOut, outLen: FfiOut, outIdx: FfiOut): bigint;
97
+ galley_diagnostic_recovery_occurrence(session: Deno.PointerValue, outParent: FfiOut, outParentLen: FfiOut, outRhs: FfiOut, outSym: FfiOut, outVar: FfiOut, outVarLen: FfiOut): bigint;
98
+ galley_recorded_diagnostic_count(session: Deno.PointerValue): bigint;
99
+ galley_recorded_diagnostic_kind(session: Deno.PointerValue, diagIndex: bigint): bigint;
100
+ galley_recorded_diagnostic_position(session: Deno.PointerValue, diagIndex: bigint, outLine: FfiOut, outCol: FfiOut): bigint;
101
+ galley_recorded_unexpected_token(session: Deno.PointerValue, diagIndex: bigint, outData: FfiOut, outLen: FfiOut): bigint;
102
+ galley_recorded_diagnostic_message(session: Deno.PointerValue, diagIndex: bigint, out: FfiOut): bigint;
103
+ galley_recorded_indentation(session: Deno.PointerValue, diagIndex: bigint, outSpaces: FfiOut, outWidth: FfiOut): bigint;
104
+ galley_recorded_semantic(session: Deno.PointerValue, diagIndex: bigint, outVariable: FfiOut, outVariableLen: FfiOut, outMessage: FfiOut, outMessageLen: FfiOut): bigint;
105
+ galley_recorded_expected_count(session: Deno.PointerValue, diagIndex: bigint): bigint;
106
+ galley_recorded_expected_token(session: Deno.PointerValue, diagIndex: bigint, tokenIndex: bigint, outData: FfiOut, outLen: FfiOut): bigint;
107
+ galley_recorded_context_count(session: Deno.PointerValue, diagIndex: bigint): bigint;
108
+ galley_recorded_context_name(session: Deno.PointerValue, diagIndex: bigint, ctxIndex: bigint, outData: FfiOut, outLen: FfiOut): bigint;
109
+ galley_recorded_recovery_kind(session: Deno.PointerValue, diagIndex: bigint): bigint;
110
+ galley_recorded_recovery_terminal(session: Deno.PointerValue, diagIndex: bigint, outData: FfiOut, outLen: FfiOut): bigint;
111
+ galley_recorded_recovery_resume(session: Deno.PointerValue, diagIndex: bigint, out: FfiOut): bigint;
112
+ galley_recorded_recovery_lhs_variable(session: Deno.PointerValue, diagIndex: bigint, outData: FfiOut, outLen: FfiOut): bigint;
113
+ galley_recorded_recovery_production(session: Deno.PointerValue, diagIndex: bigint, outVar: FfiOut, outLen: FfiOut, outIdx: FfiOut): bigint;
114
+ galley_recorded_recovery_occurrence(session: Deno.PointerValue, diagIndex: bigint, outParent: FfiOut, outParentLen: FfiOut, outRhs: FfiOut, outSym: FfiOut, outVar: FfiOut, outVarLen: FfiOut): bigint;
115
+ galley_tree_append_children(session: Deno.PointerValue, parent: bigint, first: bigint): bigint;
116
+ galley_tree_insert_before(session: Deno.PointerValue, target: bigint, first: bigint): bigint;
117
+ galley_tree_insert_after(session: Deno.PointerValue, target: bigint, first: bigint): bigint;
118
+ galley_tree_remove_siblings(session: Deno.PointerValue, node: bigint, count: number, outHead: FfiOut): bigint;
119
+ galley_tree_remove_self(session: Deno.PointerValue, node: bigint, outHead: FfiOut): bigint;
120
+ galley_tree_promote_children_over_wrapper(session: Deno.PointerValue, wrapper: bigint, outHead: FfiOut): bigint;
121
+ galley_tree_clean_children(session: Deno.PointerValue, node: bigint, outHead: FfiOut): bigint;
122
+ galley_tree_unlink_wrapper(session: Deno.PointerValue, wrapper: bigint): bigint;
123
+ galley_tree_insert_children_at(session: Deno.PointerValue, parent: bigint, index: number, first: bigint): bigint;
124
+ galley_tree_remove_children_at(session: Deno.PointerValue, parent: bigint, index: number, count: number, outHead: FfiOut): bigint;
125
+ galley_procedure_session(args: Deno.PointerValue): Deno.PointerValue;
126
+ galley_procedure_current_node(args: Deno.PointerValue): bigint;
127
+ galley_procedure_set_current_node(args: Deno.PointerValue, node: bigint): void;
128
+ galley_procedure_drop_self(args: Deno.PointerValue): bigint;
129
+ galley_procedure_drop_children(args: Deno.PointerValue): bigint;
130
+ galley_procedure_drop_if_empty(args: Deno.PointerValue): bigint;
131
+ galley_procedure_replace_with_children(args: Deno.PointerValue): bigint;
132
+ galley_procedure_context_line(args: Deno.PointerValue): number;
133
+ galley_procedure_context_column(args: Deno.PointerValue): number;
134
+ galley_procedure_report_semantic_error(args: Deno.PointerValue, message: FfiOut, messageLen: number): bigint;
135
+ galley_install_js_dispatch(callback: Deno.PointerValue): void;
136
+ galley_install_js_dispatch_id?(callback: Deno.PointerValue): void;
137
+ galley_js_procedure_count?(): number;
138
+ galley_js_procedure_name_ptr?(index: number): bigint;
139
+ galley_js_procedure_name_len?(index: number): bigint;
140
+ galley_js_procedure_enable?(name: FfiOut, nameLen: number | bigint): number;
141
+ galley_js_procedure_clear?(): void;
142
+ }
143
+
144
+ // --- library discovery -------------------------------------------------
145
+ // One place, named up front: an explicit path or GALLEY_LIBRARY_PATH.
146
+ // Anything else is a loud error, never a search.
147
+
148
+ const BUILD_HINT =
149
+ "Build it first: deno task build in your language dir\n" +
150
+ `or set GALLEY_LIBRARY_PATH=/path/to/${libFileName()}`;
151
+
152
+ export function libFileName(base = "galley-js-deno"): string {
153
+ return artifactFileName(base, Deno.build.os);
154
+ }
155
+
156
+ function exists(filePath: string): boolean {
157
+ try {
158
+ Deno.statSync(filePath);
159
+ return true;
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
165
+ export function findLibrary(explicit?: string): string {
166
+ // Deno reports the path it was given (no resolve step), as before.
167
+ return resolveArtifact(explicit, {
168
+ getEnv: (name) => Deno.env.get(name),
169
+ resolvePath: (candidate) => candidate,
170
+ existsSync: exists,
171
+ buildHint: BUILD_HINT,
172
+ });
173
+ }
174
+
175
+ // --- loader ------------------------------------------------------------
176
+
177
+ const BASE_SYMBOLS = {
178
+ galley_version: { parameters: [], result: "pointer" },
179
+ galley_parser_type: { parameters: [], result: "i64" },
180
+ galley_error_recovery_mode: { parameters: [], result: "i64" },
181
+ galley_has_ast: { parameters: [], result: "i32" },
182
+ galley_has_procedures: { parameters: [], result: "i32" },
183
+ galley_allows_no_ast_tree_procedures: { parameters: [], result: "i32" },
184
+ galley_source_retention_enabled: { parameters: [], result: "i32" },
185
+ galley_has_position_tracking: { parameters: [], result: "i32" },
186
+ galley_has_input_streaming: { parameters: [], result: "i32" },
187
+ galley_uses_verbatim: { parameters: [], result: "i32" },
188
+ galley_stack_overflow_recovery_available: { parameters: [], result: "i32" },
189
+ galley_symbol_count: { parameters: [], result: "u64" },
190
+ galley_variable_count: { parameters: [], result: "u64" },
191
+ galley_status_string: { parameters: ["i64"], result: "pointer" },
192
+ galley_symbol_name: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
193
+ galley_symbol_is_terminal: { parameters: ["pointer", "u64"], result: "i32" },
194
+ galley_variable_name: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
195
+ galley_session_create: { parameters: [], result: "pointer" },
196
+ galley_session_create_ex: { parameters: ["buffer"], result: "pointer" },
197
+ galley_session_destroy: { parameters: ["pointer"], result: "void" },
198
+ galley_session_set_message_override: { parameters: ["pointer", "buffer", "usize", "buffer", "usize"], result: "i64" },
199
+ galley_parse: { parameters: ["pointer", "buffer", "usize"], result: "i64" },
200
+ galley_parse_file: { parameters: ["pointer", "buffer"], result: "i64" },
201
+ galley_last_position: { parameters: ["pointer", "buffer", "buffer"], result: "i64" },
202
+ galley_node_count: { parameters: ["pointer"], result: "u64" },
203
+ galley_reserve_nodes: { parameters: ["pointer", "u64"], result: "i64" },
204
+ galley_node_capacity: { parameters: ["pointer"], result: "u64" },
205
+ galley_root_node: { parameters: ["pointer"], result: "u64" },
206
+ galley_node_is_valid: { parameters: ["pointer", "u64"], result: "i32" },
207
+ galley_node_child_count: { parameters: ["pointer", "u64"], result: "u32" },
208
+ galley_node_first_child: { parameters: ["pointer", "u64"], result: "u64" },
209
+ galley_node_last_child: { parameters: ["pointer", "u64"], result: "u64" },
210
+ galley_node_next_sibling: { parameters: ["pointer", "u64"], result: "u64" },
211
+ galley_node_prior_sibling: { parameters: ["pointer", "u64"], result: "u64" },
212
+ galley_node_parent: { parameters: ["pointer", "u64"], result: "u64" },
213
+ galley_tree_snapshot: {
214
+ parameters: ["pointer", "buffer", "buffer", "buffer", "buffer", "buffer", "buffer", "buffer", "u64"],
215
+ result: "i64",
216
+ },
217
+ galley_walker_create: { parameters: ["pointer", "u64", "i32"], result: "pointer" },
218
+ galley_walker_next: { parameters: ["pointer", "buffer", "buffer", "buffer"], result: "i32" },
219
+ galley_walker_skip_children: { parameters: ["pointer"], result: "void" },
220
+ galley_walker_destroy: { parameters: ["pointer"], result: "void" },
221
+ galley_node_span: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
222
+ galley_node_symbol_name: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
223
+ galley_node_variable_index: { parameters: ["pointer", "u64"], result: "i64" },
224
+ galley_node_text: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
225
+ galley_node_line_column: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
226
+ galley_has_diagnostic: { parameters: ["pointer"], result: "i32" },
227
+ galley_diagnostic_kind: { parameters: ["pointer"], result: "i64" },
228
+ galley_diagnostic_message: { parameters: ["pointer", "buffer"], result: "i64" },
229
+ galley_diagnostic_message_ansi: { parameters: ["pointer", "buffer"], result: "i64" },
230
+ galley_diagnostic_position: { parameters: ["pointer", "buffer", "buffer"], result: "i64" },
231
+ galley_diagnostic_unexpected_token: { parameters: ["pointer", "buffer", "buffer"], result: "i64" },
232
+ galley_diagnostic_expected_count: { parameters: ["pointer"], result: "i64" },
233
+ galley_diagnostic_expected_at: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
234
+ galley_diagnostic_context_count: { parameters: ["pointer"], result: "i64" },
235
+ galley_diagnostic_context_at: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
236
+ galley_diagnostic_indentation: { parameters: ["pointer", "buffer", "buffer"], result: "i64" },
237
+ galley_syntax_error_count: { parameters: ["pointer"], result: "i64" },
238
+ galley_semantic_error_count: { parameters: ["pointer"], result: "i64" },
239
+ galley_diagnostic_semantic: { parameters: ["pointer", "buffer", "buffer", "buffer", "buffer"], result: "i64" },
240
+ galley_diagnostic_recovery_kind: { parameters: ["pointer"], result: "i64" },
241
+ galley_diagnostic_recovery_terminal: { parameters: ["pointer", "buffer", "buffer"], result: "i64" },
242
+ galley_diagnostic_recovery_resume: { parameters: ["pointer", "buffer"], result: "i64" },
243
+ galley_diagnostic_recovery_lhs_variable: { parameters: ["pointer", "buffer", "buffer"], result: "i64" },
244
+ galley_diagnostic_recovery_production: { parameters: ["pointer", "buffer", "buffer", "buffer"], result: "i64" },
245
+ galley_diagnostic_recovery_occurrence: { parameters: ["pointer", "buffer", "buffer", "buffer", "buffer", "buffer", "buffer"], result: "i64" },
246
+ galley_recorded_diagnostic_count: { parameters: ["pointer"], result: "i64" },
247
+ galley_recorded_diagnostic_kind: { parameters: ["pointer", "u64"], result: "i64" },
248
+ galley_recorded_diagnostic_position: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
249
+ galley_recorded_unexpected_token: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
250
+ galley_recorded_diagnostic_message: { parameters: ["pointer", "u64", "buffer"], result: "i64" },
251
+ galley_recorded_indentation: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
252
+ galley_recorded_semantic: { parameters: ["pointer", "u64", "buffer", "buffer", "buffer", "buffer"], result: "i64" },
253
+ galley_recorded_expected_count: { parameters: ["pointer", "u64"], result: "i64" },
254
+ galley_recorded_expected_token: { parameters: ["pointer", "u64", "u64", "buffer", "buffer"], result: "i64" },
255
+ galley_recorded_context_count: { parameters: ["pointer", "u64"], result: "i64" },
256
+ galley_recorded_context_name: { parameters: ["pointer", "u64", "u64", "buffer", "buffer"], result: "i64" },
257
+ // NB: the implementation exports galley_recorded_diagnostic_recovery_kind
258
+ // (the header's shorter name is stale); `name` maps to the true symbol.
259
+ galley_recorded_recovery_kind: { name: "galley_recorded_diagnostic_recovery_kind", parameters: ["pointer", "u64"], result: "i64" },
260
+ galley_recorded_recovery_terminal: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
261
+ galley_recorded_recovery_resume: { parameters: ["pointer", "u64", "buffer"], result: "i64" },
262
+ galley_recorded_recovery_lhs_variable: { parameters: ["pointer", "u64", "buffer", "buffer"], result: "i64" },
263
+ galley_recorded_recovery_production: { parameters: ["pointer", "u64", "buffer", "buffer", "buffer"], result: "i64" },
264
+ galley_recorded_recovery_occurrence: { parameters: ["pointer", "u64", "buffer", "buffer", "buffer", "buffer", "buffer", "buffer"], result: "i64" },
265
+ galley_tree_append_children: { parameters: ["pointer", "u64", "u64"], result: "i64" },
266
+ galley_tree_insert_before: { parameters: ["pointer", "u64", "u64"], result: "i64" },
267
+ galley_tree_insert_after: { parameters: ["pointer", "u64", "u64"], result: "i64" },
268
+ galley_tree_remove_siblings: { parameters: ["pointer", "u64", "usize", "buffer"], result: "i64" },
269
+ galley_tree_remove_self: { parameters: ["pointer", "u64", "buffer"], result: "i64" },
270
+ galley_tree_promote_children_over_wrapper: { parameters: ["pointer", "u64", "buffer"], result: "i64" },
271
+ galley_tree_clean_children: { parameters: ["pointer", "u64", "buffer"], result: "i64" },
272
+ galley_tree_unlink_wrapper: { parameters: ["pointer", "u64"], result: "i64" },
273
+ galley_tree_insert_children_at: { parameters: ["pointer", "u64", "usize", "u64"], result: "i64" },
274
+ galley_tree_remove_children_at: { parameters: ["pointer", "u64", "usize", "usize", "buffer"], result: "i64" },
275
+ galley_procedure_session: { parameters: ["pointer"], result: "pointer" },
276
+ galley_procedure_current_node: { parameters: ["pointer"], result: "u64" },
277
+ galley_procedure_set_current_node: { parameters: ["pointer", "u64"], result: "void" },
278
+ galley_procedure_drop_self: { parameters: ["pointer"], result: "i64" },
279
+ galley_procedure_drop_children: { parameters: ["pointer"], result: "i64" },
280
+ galley_procedure_drop_if_empty: { parameters: ["pointer"], result: "i64" },
281
+ galley_procedure_replace_with_children: { parameters: ["pointer"], result: "i64" },
282
+ galley_procedure_context_line: { parameters: ["pointer"], result: "u32" },
283
+ galley_procedure_context_column: { parameters: ["pointer"], result: "u32" },
284
+ galley_procedure_report_semantic_error: { parameters: ["pointer", "buffer", "usize"], result: "i64" },
285
+ } as const;
286
+
287
+ const DISPATCH_SYMBOL = {
288
+ galley_install_js_dispatch: { parameters: ["function"], result: "void" },
289
+ } as const;
290
+
291
+ const ID_DISPATCH_SYMBOL = {
292
+ galley_install_js_dispatch_id: { parameters: ["function"], result: "void" },
293
+ galley_js_procedure_count: { parameters: [], result: "u32" },
294
+ galley_js_procedure_name_ptr: { parameters: ["u32"], result: "u64" },
295
+ galley_js_procedure_name_len: { parameters: ["u32"], result: "u64" },
296
+ } as const;
297
+
298
+ const SELECTIVE_SYMBOL = {
299
+ galley_js_procedure_enable: { parameters: ["buffer", "usize"], result: "i32" },
300
+ galley_js_procedure_clear: { parameters: [], result: "void" },
301
+ } as const;
302
+
303
+ function openNative(libPath: string, level: number) {
304
+ const symbols =
305
+ level >= 3
306
+ ? { ...BASE_SYMBOLS, ...ID_DISPATCH_SYMBOL, ...SELECTIVE_SYMBOL }
307
+ : level >= 2
308
+ ? { ...BASE_SYMBOLS, ...DISPATCH_SYMBOL, ...SELECTIVE_SYMBOL }
309
+ : level >= 1
310
+ ? { ...BASE_SYMBOLS, ...DISPATCH_SYMBOL }
311
+ : BASE_SYMBOLS;
312
+ return Deno.dlopen(libPath, symbols);
313
+ }
314
+
315
+ // --- read helpers ----------------------------------------------------------
316
+
317
+ function readBytes(addr: bigint, len: bigint): Uint8Array {
318
+ if (addr === 0n || len === 0n) return new Uint8Array(0);
319
+ const ptr = Deno.UnsafePointer.create(addr);
320
+ if (ptr === null) return new Uint8Array(0);
321
+ const view = new Deno.UnsafePointerView(ptr);
322
+ // slice(0) copies: the native memory dies on the next parse.
323
+ return new Uint8Array(view.getArrayBuffer(Number(len)).slice(0));
324
+ }
325
+
326
+ function readCString(ptr: Deno.PointerValue): string | null {
327
+ if (ptr === null) return null;
328
+ return new Deno.UnsafePointerView(ptr).getCString();
329
+ }
330
+
331
+ function ptrOut(): BigUint64Array {
332
+ return new BigUint64Array(1);
333
+ }
334
+
335
+ function lenOut(): BigUint64Array {
336
+ return new BigUint64Array(1);
337
+ }
338
+
339
+ function u32Out(): Uint32Array {
340
+ return new Uint32Array(1);
341
+ }
342
+
343
+ function i64Out(): BigInt64Array {
344
+ return new BigInt64Array(1);
345
+ }
346
+
347
+ // --- FfiPort implementation ----------------------------------------------
348
+
349
+ export class DenoPort implements FfiPort {
350
+ readonly native: GalleySymbols;
351
+ readonly libraryPath: string;
352
+ readonly supportsDispatch: boolean;
353
+
354
+ constructor(native: GalleySymbols, libraryPath: string, supportsDispatch: boolean) {
355
+ this.native = native;
356
+ this.libraryPath = libraryPath;
357
+ this.supportsDispatch = supportsDispatch;
358
+ }
359
+
360
+ syncProcedures(names: string[]): void {
361
+ if (typeof this.native.galley_js_procedure_clear !== "function") return;
362
+ if (typeof this.native.galley_js_procedure_enable !== "function") return;
363
+ this.native.galley_js_procedure_clear();
364
+ for (const name of names) {
365
+ this.native.galley_js_procedure_enable(textEncoder.encode(name), name.length);
366
+ }
367
+ // Warm the ID table outside any parse so the hot path never queries.
368
+ this.procedureNames();
369
+ }
370
+
371
+ #procedureNameTable: string[] | null = null;
372
+
373
+ procedureNames(): string[] {
374
+ if (this.#procedureNameTable !== null) return this.#procedureNameTable;
375
+ const table: string[] = [];
376
+ if (
377
+ typeof this.native.galley_js_procedure_count === "function" &&
378
+ typeof this.native.galley_js_procedure_name_ptr === "function" &&
379
+ typeof this.native.galley_js_procedure_name_len === "function"
380
+ ) {
381
+ const n = this.native.galley_js_procedure_count();
382
+ for (let i = 0; i < n; i++) {
383
+ const ptrValue = this.native.galley_js_procedure_name_ptr(i);
384
+ if (ptrValue === 0n) break;
385
+ const len = this.native.galley_js_procedure_name_len(i);
386
+ table.push(textDecoder.decode(readBytes(ptrValue, len)));
387
+ }
388
+ }
389
+ this.#procedureNameTable = table;
390
+ return table;
391
+ }
392
+
393
+ // -- module-level queries --------------------------------------------
394
+
395
+ version(): string {
396
+ return readCString(this.native.galley_version()) ?? "";
397
+ }
398
+
399
+ parserType(): number {
400
+ return Number(this.native.galley_parser_type());
401
+ }
402
+
403
+ errorRecoveryMode(): number {
404
+ return Number(this.native.galley_error_recovery_mode());
405
+ }
406
+
407
+ hasAst(): boolean {
408
+ return this.native.galley_has_ast() !== 0;
409
+ }
410
+
411
+ hasProcedures(): boolean {
412
+ return this.native.galley_has_procedures() !== 0;
413
+ }
414
+
415
+ allowsNoAstTreeProcedures(): boolean {
416
+ return this.native.galley_allows_no_ast_tree_procedures() !== 0;
417
+ }
418
+
419
+ sourceRetentionEnabled(): boolean {
420
+ return this.native.galley_source_retention_enabled() !== 0;
421
+ }
422
+
423
+ hasPositionTracking(): boolean {
424
+ return this.native.galley_has_position_tracking() !== 0;
425
+ }
426
+
427
+ hasInputStreaming(): boolean {
428
+ return this.native.galley_has_input_streaming() !== 0;
429
+ }
430
+
431
+ usesVerbatim(): boolean {
432
+ return this.native.galley_uses_verbatim() !== 0;
433
+ }
434
+
435
+ stackOverflowRecoveryAvailable(): boolean {
436
+ return this.native.galley_stack_overflow_recovery_available() !== 0;
437
+ }
438
+
439
+ symbolCount(): number {
440
+ return Number(this.native.galley_symbol_count());
441
+ }
442
+
443
+ variableCount(): number {
444
+ return Number(this.native.galley_variable_count());
445
+ }
446
+
447
+ statusString(status: number): string | null {
448
+ return readCString(this.native.galley_status_string(BigInt(status)));
449
+ }
450
+
451
+ // -- sessions ---------------------------------------------------------
452
+
453
+ createSession(options: SessionCOptions | null): Handle {
454
+ let handle: Deno.PointerValue;
455
+ if (options === null) {
456
+ handle = this.native.galley_session_create();
457
+ } else {
458
+ // GalleyCOptions layout: int32 x5, double, uint64 (40 bytes, LE).
459
+ const buf = new ArrayBuffer(40);
460
+ const view = new DataView(buf);
461
+ view.setInt32(0, options.maxErrors, true);
462
+ view.setInt32(4, options.recoveryWindow, true);
463
+ view.setInt32(8, options.stackOverflowRecovery, true);
464
+ view.setUint32(12, options.syntaxErrorStackDepth, true);
465
+ view.setInt32(16, options.verbosity, true);
466
+ view.setFloat64(24, options.astPreallocationRatio, true);
467
+ view.setBigUint64(32, options.astPreallocationCap, true);
468
+ handle = this.native.galley_session_create_ex(new Uint8Array(buf));
469
+ }
470
+ if (handle === null) return null;
471
+ return handle;
472
+ }
473
+
474
+ destroySession(handle: Handle): void {
475
+ this.native.galley_session_destroy(handle as Deno.PointerValue);
476
+ }
477
+
478
+ setMessageOverride(handle: Handle, name: Uint8Array, message: Uint8Array): number {
479
+ return Number(
480
+ this.native.galley_session_set_message_override(handle as Deno.PointerValue, name, name.length, message, message.length),
481
+ );
482
+ }
483
+
484
+ // -- parsing ----------------------------------------------------------
485
+
486
+ parse(handle: Handle, data: Uint8Array): number {
487
+ return Number(this.native.galley_parse(handle as Deno.PointerValue, data, data.length));
488
+ }
489
+
490
+ parseFile(handle: Handle, filePath: string): number {
491
+ const bytes = textEncoder.encode(filePath);
492
+ const nul = new Uint8Array(bytes.length + 1);
493
+ nul.set(bytes);
494
+ return Number(this.native.galley_parse_file(handle as Deno.PointerValue, nul));
495
+ }
496
+
497
+ lastPosition(handle: Handle): [number, number] | null {
498
+ const outLine = u32Out();
499
+ const outCol = u32Out();
500
+ if (this.native.galley_last_position(handle as Deno.PointerValue, outLine, outCol) < 0n) return null;
501
+ return [outLine[0], outCol[0]];
502
+ }
503
+
504
+ // -- arena and navigation ----------------------------------------------
505
+
506
+ nodeCount(handle: Handle): number {
507
+ return Number(this.native.galley_node_count(handle as Deno.PointerValue));
508
+ }
509
+
510
+ reserveNodes(handle: Handle, capacity: bigint): number {
511
+ return Number(this.native.galley_reserve_nodes(handle as Deno.PointerValue, capacity));
512
+ }
513
+
514
+ nodeCapacity(handle: Handle): number {
515
+ return Number(this.native.galley_node_capacity(handle as Deno.PointerValue));
516
+ }
517
+
518
+ rootNode(handle: Handle): bigint {
519
+ return this.native.galley_root_node(handle as Deno.PointerValue);
520
+ }
521
+
522
+ nodeValid(handle: Handle, node: bigint): boolean {
523
+ return this.native.galley_node_is_valid(handle as Deno.PointerValue, node) !== 0;
524
+ }
525
+
526
+ childCount(handle: Handle, node: bigint): number {
527
+ return this.native.galley_node_child_count(handle as Deno.PointerValue, node);
528
+ }
529
+
530
+ firstChild(handle: Handle, node: bigint): bigint {
531
+ return this.native.galley_node_first_child(handle as Deno.PointerValue, node);
532
+ }
533
+
534
+ lastChild(handle: Handle, node: bigint): bigint {
535
+ return this.native.galley_node_last_child(handle as Deno.PointerValue, node);
536
+ }
537
+
538
+ nextSibling(handle: Handle, node: bigint): bigint {
539
+ return this.native.galley_node_next_sibling(handle as Deno.PointerValue, node);
540
+ }
541
+
542
+ priorSibling(handle: Handle, node: bigint): bigint {
543
+ return this.native.galley_node_prior_sibling(handle as Deno.PointerValue, node);
544
+ }
545
+
546
+ parent(handle: Handle, node: bigint): bigint {
547
+ return this.native.galley_node_parent(handle as Deno.PointerValue, node);
548
+ }
549
+
550
+ treeSnapshot(handle: Handle): TreeSnapshot {
551
+ for (let attempt = 0; attempt < 2; attempt++) {
552
+ const count = this.nodeCount(handle);
553
+ const parent = new BigUint64Array(count);
554
+ const firstChild = new BigUint64Array(count);
555
+ const next = new BigUint64Array(count);
556
+ const childCount = new Uint32Array(count);
557
+ const variable = new BigInt64Array(count);
558
+ const spanStart = new BigUint64Array(count);
559
+ const spanLen = new BigUint64Array(count);
560
+ const total = this.native.galley_tree_snapshot(
561
+ handle as Deno.PointerValue, parent, firstChild, next, childCount,
562
+ variable, spanStart, spanLen, BigInt(count),
563
+ );
564
+ if (total < 0n) throw new GalleyError("galley_tree_snapshot failed", Number(total));
565
+ if (total === BigInt(count)) {
566
+ return { count, parent, firstChild, next, childCount, variable, spanStart, spanLen };
567
+ }
568
+ }
569
+ throw new GalleyError("node count changed during galley_tree_snapshot", -8);
570
+ }
571
+
572
+ // -- walker ------------------------------------------------------------
573
+
574
+ walkerCreate(handle: Handle, node: bigint, skipSemanticErrors: boolean): Handle | null {
575
+ const walker = this.native.galley_walker_create(handle as Deno.PointerValue, node, skipSemanticErrors ? 1 : 0);
576
+ if (walker === null) return null;
577
+ return walker;
578
+ }
579
+
580
+ walkerNext(walker: Handle): WalkedStep | null {
581
+ const outNode = lenOut();
582
+ const outDepth = u32Out();
583
+ const outFlag = u32Out();
584
+ if (this.native.galley_walker_next(walker as Deno.PointerValue, outNode, outDepth, outFlag) === 0) return null;
585
+ return { node: outNode[0], depth: outDepth[0], isSemanticError: outFlag[0] !== 0 };
586
+ }
587
+
588
+ walkerSkipChildren(walker: Handle): void {
589
+ this.native.galley_walker_skip_children(walker as Deno.PointerValue);
590
+ }
591
+
592
+ walkerDestroy(walker: Handle): void {
593
+ this.native.galley_walker_destroy(walker as Deno.PointerValue);
594
+ }
595
+
596
+ // -- node accessors -----------------------------------------------------
597
+
598
+ #tryCopyBytes(fn: (outData: BigUint64Array, outLen: BigUint64Array) => bigint): Uint8Array | null {
599
+ const outData = ptrOut();
600
+ const outLen = lenOut();
601
+ if (fn(outData, outLen) < 0n) return null;
602
+ if (outData[0] === 0n) return null;
603
+ return readBytes(outData[0], outLen[0]);
604
+ }
605
+
606
+ #readSemanticPair(
607
+ fn: (outVariable: BigUint64Array, outVariableLen: BigUint64Array, outMessage: BigUint64Array, outMessageLen: BigUint64Array) => bigint,
608
+ ): [string, string] | null {
609
+ const outVariable = ptrOut();
610
+ const outVariableLen = lenOut();
611
+ const outMessage = ptrOut();
612
+ const outMessageLen = lenOut();
613
+ if (fn(outVariable, outVariableLen, outMessage, outMessageLen) < 0n) return null;
614
+ if (outVariable[0] === 0n || outMessage[0] === 0n) return null;
615
+ return [
616
+ textDecoder.decode(readBytes(outVariable[0], outVariableLen[0])),
617
+ textDecoder.decode(readBytes(outMessage[0], outMessageLen[0])),
618
+ ];
619
+ }
620
+
621
+ nodeSymbolName(handle: Handle, node: bigint): Uint8Array | null {
622
+ const h = handle as Deno.PointerValue;
623
+ const outData = ptrOut();
624
+ const outLen = lenOut();
625
+ if (this.native.galley_node_symbol_name(h, node, outData, outLen) < 0n) return null;
626
+ return readBytes(outData[0], outLen[0]);
627
+ }
628
+
629
+ nodeText(handle: Handle, node: bigint): Uint8Array | null {
630
+ const h = handle as Deno.PointerValue;
631
+ const outData = ptrOut();
632
+ const outLen = lenOut();
633
+ if (this.native.galley_node_text(h, node, outData, outLen) < 0n) return null;
634
+ return readBytes(outData[0], outLen[0]);
635
+ }
636
+
637
+ nodeSpan(handle: Handle, node: bigint): [bigint, bigint] | null {
638
+ const outStart = lenOut();
639
+ const outLen = lenOut();
640
+ if (this.native.galley_node_span(handle as Deno.PointerValue, node, outStart, outLen) < 0n) return null;
641
+ return [outStart[0], outLen[0]];
642
+ }
643
+
644
+ nodeLineColumn(handle: Handle, node: bigint): [number, number] | null {
645
+ const outLine = u32Out();
646
+ const outCol = u32Out();
647
+ if (this.native.galley_node_line_column(handle as Deno.PointerValue, node, outLine, outCol) < 0n) return null;
648
+ return [outLine[0], outCol[0]];
649
+ }
650
+
651
+ nodeVariableIndex(handle: Handle, node: bigint): number {
652
+ return Number(this.native.galley_node_variable_index(handle as Deno.PointerValue, node));
653
+ }
654
+
655
+ symbolNameAt(handle: Handle, index: number): Uint8Array | null {
656
+ const h = handle as Deno.PointerValue;
657
+ const outData = ptrOut();
658
+ const outLen = lenOut();
659
+ if (this.native.galley_symbol_name(h, BigInt(index), outData, outLen) < 0n) return null;
660
+ return readBytes(outData[0], outLen[0]);
661
+ }
662
+
663
+ symbolIsTerminal(handle: Handle, index: number): boolean {
664
+ return this.native.galley_symbol_is_terminal(handle as Deno.PointerValue, BigInt(index)) !== 0;
665
+ }
666
+
667
+ variableNameAt(handle: Handle, index: number): Uint8Array | null {
668
+ const h = handle as Deno.PointerValue;
669
+ const outData = ptrOut();
670
+ const outLen = lenOut();
671
+ if (this.native.galley_variable_name(h, BigInt(index), outData, outLen) < 0n) return null;
672
+ return readBytes(outData[0], outLen[0]);
673
+ }
674
+
675
+ // -- diagnostics ---------------------------------------------------------
676
+
677
+ hasDiagnostic(handle: Handle): boolean {
678
+ return this.native.galley_has_diagnostic(handle as Deno.PointerValue) !== 0;
679
+ }
680
+
681
+ diagnosticKind(handle: Handle): number {
682
+ return Number(this.native.galley_diagnostic_kind(handle as Deno.PointerValue));
683
+ }
684
+
685
+ diagnosticMessage(handle: Handle): string | null {
686
+ const out = ptrOut();
687
+ if (this.native.galley_diagnostic_message(handle as Deno.PointerValue, out) !== 0n) return null;
688
+ return readCString(Deno.UnsafePointer.create(out[0]));
689
+ }
690
+
691
+ diagnosticMessageAnsi(handle: Handle): string | null {
692
+ const out = ptrOut();
693
+ if (this.native.galley_diagnostic_message_ansi(handle as Deno.PointerValue, out) !== 0n) return null;
694
+ return readCString(Deno.UnsafePointer.create(out[0]));
695
+ }
696
+
697
+ diagnosticPosition(handle: Handle): [number, number] | null {
698
+ const outLine = u32Out();
699
+ const outCol = u32Out();
700
+ if (this.native.galley_diagnostic_position(handle as Deno.PointerValue, outLine, outCol) < 0n) return null;
701
+ return [outLine[0], outCol[0]];
702
+ }
703
+
704
+ diagnosticUnexpectedToken(handle: Handle): Uint8Array | null {
705
+ const h = handle as Deno.PointerValue;
706
+ return this.#tryCopyBytes((od, ol) => this.native.galley_diagnostic_unexpected_token(h, od, ol));
707
+ }
708
+
709
+ diagnosticExpectedCount(handle: Handle): number {
710
+ return Number(this.native.galley_diagnostic_expected_count(handle as Deno.PointerValue));
711
+ }
712
+
713
+ diagnosticExpectedAt(handle: Handle, index: number): Uint8Array | null {
714
+ const h = handle as Deno.PointerValue;
715
+ return this.#tryCopyBytes((od, ol) => this.native.galley_diagnostic_expected_at(h, BigInt(index), od, ol));
716
+ }
717
+
718
+ diagnosticContextCount(handle: Handle): number {
719
+ return Number(this.native.galley_diagnostic_context_count(handle as Deno.PointerValue));
720
+ }
721
+
722
+ diagnosticContextAt(handle: Handle, index: number): Uint8Array | null {
723
+ const h = handle as Deno.PointerValue;
724
+ return this.#tryCopyBytes((od, ol) => this.native.galley_diagnostic_context_at(h, BigInt(index), od, ol));
725
+ }
726
+
727
+ syntaxErrorCount(handle: Handle): number {
728
+ return Number(this.native.galley_syntax_error_count(handle as Deno.PointerValue));
729
+ }
730
+
731
+ semanticErrorCount(handle: Handle): number {
732
+ return Number(this.native.galley_semantic_error_count(handle as Deno.PointerValue));
733
+ }
734
+
735
+ diagnosticSemantic(handle: Handle): [string, string] | null {
736
+ const h = handle as Deno.PointerValue;
737
+ return this.#readSemanticPair((ov, ovl, om, oml) => this.native.galley_diagnostic_semantic(h, ov, ovl, om, oml));
738
+ }
739
+
740
+ diagnosticIndentation(handle: Handle): [number, number] | null {
741
+ const outSpaces = u32Out();
742
+ const outWidth = u32Out();
743
+ if (this.native.galley_diagnostic_indentation(handle as Deno.PointerValue, outSpaces, outWidth) !== 0n) return null;
744
+ return [outSpaces[0], outWidth[0]];
745
+ }
746
+
747
+ diagnosticRecoveryKind(handle: Handle): number {
748
+ return Number(this.native.galley_diagnostic_recovery_kind(handle as Deno.PointerValue));
749
+ }
750
+
751
+ diagnosticRecoveryTerminal(handle: Handle): Uint8Array | null {
752
+ const h = handle as Deno.PointerValue;
753
+ return this.#tryCopyBytes((od, ol) => this.native.galley_diagnostic_recovery_terminal(h, od, ol));
754
+ }
755
+
756
+ diagnosticRecoveryResume(handle: Handle): number | null {
757
+ const out = i64Out();
758
+ if (this.native.galley_diagnostic_recovery_resume(handle as Deno.PointerValue, out) !== 0n) return null;
759
+ return Number(out[0]);
760
+ }
761
+
762
+ diagnosticRecoveryLhsVariable(handle: Handle): string | null {
763
+ const h = handle as Deno.PointerValue;
764
+ const outData = ptrOut();
765
+ const outLen = lenOut();
766
+ if (this.native.galley_diagnostic_recovery_lhs_variable(h, outData, outLen) < 0n || outData[0] === 0n) return null;
767
+ return textDecoder.decode(readBytes(outData[0], outLen[0]));
768
+ }
769
+
770
+ diagnosticRecoveryProduction(handle: Handle): [string, number] | null {
771
+ const h = handle as Deno.PointerValue;
772
+ const outVar = ptrOut();
773
+ const outLen = lenOut();
774
+ const outIdx = u32Out();
775
+ if (this.native.galley_diagnostic_recovery_production(h, outVar, outLen, outIdx) !== 0n) return null;
776
+ return [textDecoder.decode(readBytes(outVar[0], outLen[0])), outIdx[0]];
777
+ }
778
+
779
+ diagnosticRecoveryOccurrence(handle: Handle): [string, number, number, string] | null {
780
+ const h = handle as Deno.PointerValue;
781
+ const outParent = ptrOut();
782
+ const outParentLen = lenOut();
783
+ const outRhs = u32Out();
784
+ const outSym = u32Out();
785
+ const outVar = ptrOut();
786
+ const outVarLen = lenOut();
787
+ if (this.native.galley_diagnostic_recovery_occurrence(h, outParent, outParentLen, outRhs, outSym, outVar, outVarLen) !== 0n) return null;
788
+ return [
789
+ textDecoder.decode(readBytes(outParent[0], outParentLen[0])),
790
+ outRhs[0],
791
+ outSym[0],
792
+ textDecoder.decode(readBytes(outVar[0], outVarLen[0])),
793
+ ];
794
+ }
795
+
796
+ recordedDiagnosticCount(handle: Handle): number {
797
+ return Number(this.native.galley_recorded_diagnostic_count(handle as Deno.PointerValue));
798
+ }
799
+
800
+ recordedDiagnosticKind(handle: Handle, diagIndex: number): number {
801
+ return Number(this.native.galley_recorded_diagnostic_kind(handle as Deno.PointerValue, BigInt(diagIndex)));
802
+ }
803
+
804
+ recordedDiagnosticPosition(handle: Handle, diagIndex: number): [number, number] | null {
805
+ const outLine = u32Out();
806
+ const outCol = u32Out();
807
+ if (this.native.galley_recorded_diagnostic_position(handle as Deno.PointerValue, BigInt(diagIndex), outLine, outCol) < 0n) return null;
808
+ return [outLine[0], outCol[0]];
809
+ }
810
+
811
+ recordedUnexpectedToken(handle: Handle, diagIndex: number): Uint8Array | null {
812
+ const h = handle as Deno.PointerValue;
813
+ const d = BigInt(diagIndex);
814
+ return this.#tryCopyBytes((od, ol) => this.native.galley_recorded_unexpected_token(h, d, od, ol));
815
+ }
816
+
817
+ recordedDiagnosticMessage(handle: Handle, diagIndex: number): string | null {
818
+ const out = ptrOut();
819
+ if (this.native.galley_recorded_diagnostic_message(handle as Deno.PointerValue, BigInt(diagIndex), out) !== 0n) return null;
820
+ return readCString(Deno.UnsafePointer.create(out[0]));
821
+ }
822
+
823
+ recordedIndentation(handle: Handle, diagIndex: number): [number, number] | null {
824
+ const outSpaces = u32Out();
825
+ const outWidth = u32Out();
826
+ if (this.native.galley_recorded_indentation(handle as Deno.PointerValue, BigInt(diagIndex), outSpaces, outWidth) !== 0n) return null;
827
+ return [outSpaces[0], outWidth[0]];
828
+ }
829
+
830
+ recordedSemantic(handle: Handle, diagIndex: number): [string, string] | null {
831
+ const h = handle as Deno.PointerValue;
832
+ const d = BigInt(diagIndex);
833
+ return this.#readSemanticPair((ov, ovl, om, oml) => this.native.galley_recorded_semantic(h, d, ov, ovl, om, oml));
834
+ }
835
+
836
+ recordedExpectedCount(handle: Handle, diagIndex: number): number {
837
+ return Number(this.native.galley_recorded_expected_count(handle as Deno.PointerValue, BigInt(diagIndex)));
838
+ }
839
+
840
+ recordedExpectedToken(handle: Handle, diagIndex: number, tokenIndex: number): Uint8Array | null {
841
+ const h = handle as Deno.PointerValue;
842
+ const d = BigInt(diagIndex);
843
+ const t = BigInt(tokenIndex);
844
+ return this.#tryCopyBytes((od, ol) => this.native.galley_recorded_expected_token(h, d, t, od, ol));
845
+ }
846
+
847
+ recordedContextCount(handle: Handle, diagIndex: number): number {
848
+ return Number(this.native.galley_recorded_context_count(handle as Deno.PointerValue, BigInt(diagIndex)));
849
+ }
850
+
851
+ recordedContextName(handle: Handle, diagIndex: number, contextIndex: number): Uint8Array | null {
852
+ const h = handle as Deno.PointerValue;
853
+ const d = BigInt(diagIndex);
854
+ const c = BigInt(contextIndex);
855
+ return this.#tryCopyBytes((od, ol) => this.native.galley_recorded_context_name(h, d, c, od, ol));
856
+ }
857
+
858
+ recordedRecoveryKind(handle: Handle, diagIndex: number): number {
859
+ return Number(this.native.galley_recorded_recovery_kind(handle as Deno.PointerValue, BigInt(diagIndex)));
860
+ }
861
+
862
+ recordedRecoveryTerminal(handle: Handle, diagIndex: number): Uint8Array | null {
863
+ const h = handle as Deno.PointerValue;
864
+ const d = BigInt(diagIndex);
865
+ return this.#tryCopyBytes((od, ol) => this.native.galley_recorded_recovery_terminal(h, d, od, ol));
866
+ }
867
+
868
+ recordedRecoveryResume(handle: Handle, diagIndex: number): number | null {
869
+ const out = i64Out();
870
+ if (this.native.galley_recorded_recovery_resume(handle as Deno.PointerValue, BigInt(diagIndex), out) !== 0n) return null;
871
+ return Number(out[0]);
872
+ }
873
+
874
+ recordedRecoveryLhsVariable(handle: Handle, diagIndex: number): string | null {
875
+ const h = handle as Deno.PointerValue;
876
+ const d = BigInt(diagIndex);
877
+ const outData = ptrOut();
878
+ const outLen = lenOut();
879
+ if (this.native.galley_recorded_recovery_lhs_variable(h, d, outData, outLen) < 0n || outData[0] === 0n) return null;
880
+ return textDecoder.decode(readBytes(outData[0], outLen[0]));
881
+ }
882
+
883
+ recordedRecoveryProduction(handle: Handle, diagIndex: number): [string, number] | null {
884
+ const h = handle as Deno.PointerValue;
885
+ const d = BigInt(diagIndex);
886
+ const outVar = ptrOut();
887
+ const outLen = lenOut();
888
+ const outIdx = u32Out();
889
+ if (this.native.galley_recorded_recovery_production(h, d, outVar, outLen, outIdx) !== 0n) return null;
890
+ return [textDecoder.decode(readBytes(outVar[0], outLen[0])), outIdx[0]];
891
+ }
892
+
893
+ recordedRecoveryOccurrence(handle: Handle, diagIndex: number): [string, number, number, string] | null {
894
+ const h = handle as Deno.PointerValue;
895
+ const d = BigInt(diagIndex);
896
+ const outParent = ptrOut();
897
+ const outParentLen = lenOut();
898
+ const outRhs = u32Out();
899
+ const outSym = u32Out();
900
+ const outVar = ptrOut();
901
+ const outVarLen = lenOut();
902
+ if (this.native.galley_recorded_recovery_occurrence(h, d, outParent, outParentLen, outRhs, outSym, outVar, outVarLen) !== 0n) return null;
903
+ return [
904
+ textDecoder.decode(readBytes(outParent[0], outParentLen[0])),
905
+ outRhs[0],
906
+ outSym[0],
907
+ textDecoder.decode(readBytes(outVar[0], outVarLen[0])),
908
+ ];
909
+ }
910
+
911
+ // -- tree editing ----------------------------------------------------------
912
+
913
+ treeAppendChildren(handle: Handle, parent: bigint, first: bigint): number {
914
+ return Number(this.native.galley_tree_append_children(handle as Deno.PointerValue, parent, first));
915
+ }
916
+
917
+ treeInsertBefore(handle: Handle, target: bigint, first: bigint): number {
918
+ return Number(this.native.galley_tree_insert_before(handle as Deno.PointerValue, target, first));
919
+ }
920
+
921
+ treeInsertAfter(handle: Handle, target: bigint, first: bigint): number {
922
+ return Number(this.native.galley_tree_insert_after(handle as Deno.PointerValue, target, first));
923
+ }
924
+
925
+ treeRemoveSiblings(handle: Handle, node: bigint, count: number): { status: number; head: bigint } {
926
+ const outHead = lenOut();
927
+ const st = this.native.galley_tree_remove_siblings(handle as Deno.PointerValue, node, count, outHead);
928
+ return { status: Number(st), head: outHead[0] };
929
+ }
930
+
931
+ treeRemoveSelf(handle: Handle, node: bigint): { status: number; head: bigint } {
932
+ const outHead = lenOut();
933
+ const st = this.native.galley_tree_remove_self(handle as Deno.PointerValue, node, outHead);
934
+ return { status: Number(st), head: outHead[0] };
935
+ }
936
+
937
+ treePromoteChildrenOverWrapper(handle: Handle, wrapper: bigint): { status: number; head: bigint } {
938
+ const outHead = lenOut();
939
+ const st = this.native.galley_tree_promote_children_over_wrapper(handle as Deno.PointerValue, wrapper, outHead);
940
+ return { status: Number(st), head: outHead[0] };
941
+ }
942
+
943
+ treeCleanChildren(handle: Handle, node: bigint): { status: number; head: bigint } {
944
+ const outHead = lenOut();
945
+ const st = this.native.galley_tree_clean_children(handle as Deno.PointerValue, node, outHead);
946
+ return { status: Number(st), head: outHead[0] };
947
+ }
948
+
949
+ treeUnlinkWrapper(handle: Handle, wrapper: bigint): number {
950
+ return Number(this.native.galley_tree_unlink_wrapper(handle as Deno.PointerValue, wrapper));
951
+ }
952
+
953
+ treeInsertChildrenAt(handle: Handle, parent: bigint, index: number, first: bigint): number {
954
+ return Number(this.native.galley_tree_insert_children_at(handle as Deno.PointerValue, parent, index, first));
955
+ }
956
+
957
+ treeRemoveChildrenAt(handle: Handle, parent: bigint, index: number, count: number): { status: number; head: bigint } {
958
+ const outHead = lenOut();
959
+ const st = this.native.galley_tree_remove_children_at(handle as Deno.PointerValue, parent, index, count, outHead);
960
+ return { status: Number(st), head: outHead[0] };
961
+ }
962
+
963
+ // -- procedure hooks ----------------------------------------------------------
964
+
965
+ procCurrentNode(args: Handle): bigint {
966
+ return this.native.galley_procedure_current_node(args as Deno.PointerValue);
967
+ }
968
+
969
+ procSetCurrentNode(args: Handle, node: bigint): void {
970
+ this.native.galley_procedure_set_current_node(args as Deno.PointerValue, node);
971
+ }
972
+
973
+ procDropSelf(args: Handle): number {
974
+ return Number(this.native.galley_procedure_drop_self(args as Deno.PointerValue));
975
+ }
976
+
977
+ procDropChildren(args: Handle): number {
978
+ return Number(this.native.galley_procedure_drop_children(args as Deno.PointerValue));
979
+ }
980
+
981
+ procDropIfEmpty(args: Handle): number {
982
+ return Number(this.native.galley_procedure_drop_if_empty(args as Deno.PointerValue));
983
+ }
984
+
985
+ procReplaceWithChildren(args: Handle): number {
986
+ return Number(this.native.galley_procedure_replace_with_children(args as Deno.PointerValue));
987
+ }
988
+
989
+ procContextLine(args: Handle): number {
990
+ return this.native.galley_procedure_context_line(args as Deno.PointerValue);
991
+ }
992
+
993
+ procContextColumn(args: Handle): number {
994
+ return this.native.galley_procedure_context_column(args as Deno.PointerValue);
995
+ }
996
+
997
+ procReportSemanticError(args: Handle, message: Uint8Array): number {
998
+ return Number(
999
+ this.native.galley_procedure_report_semantic_error(args as Deno.PointerValue, message, message.length),
1000
+ );
1001
+ }
1002
+ }
1003
+
1004
+ const portCache = new Map<string, DenoPort>();
1005
+
1006
+ /** Port for the library at `explicitPath` (or default discovery), cached per path. */
1007
+ export function getDenoPort(explicitPath?: string): DenoPort {
1008
+ const libPath = findLibrary(explicitPath);
1009
+ const cached = portCache.get(libPath);
1010
+ if (cached) return cached;
1011
+ // Libraries built for C procedures lack the JS dispatch symbol; dlopen
1012
+ // fails on missing symbols, so fall back to a dispatch-less table.
1013
+ let native: GalleySymbols;
1014
+ let supportsDispatch = true;
1015
+ try {
1016
+ native = openNative(libPath, 3).symbols as unknown as GalleySymbols;
1017
+ } catch {
1018
+ try {
1019
+ native = openNative(libPath, 2).symbols as unknown as GalleySymbols;
1020
+ } catch {
1021
+ try {
1022
+ native = openNative(libPath, 1).symbols as unknown as GalleySymbols;
1023
+ } catch {
1024
+ native = openNative(libPath, 0).symbols as unknown as GalleySymbols;
1025
+ supportsDispatch = false;
1026
+ }
1027
+ }
1028
+ }
1029
+ const port = new DenoPort(native, libPath, supportsDispatch);
1030
+ portCache.set(libPath, port);
1031
+ return port;
1032
+ }