@sanbus/galley-core 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 +15 -0
- package/build/builder.mjs +276 -0
- package/build/fixture.mjs +85 -0
- package/build/shim.mjs +216 -0
- package/dist/artifact.d.ts +38 -0
- package/dist/artifact.js +45 -0
- package/dist/artifact.js.map +1 -0
- package/dist/constants.d.ts +34 -0
- package/dist/constants.js +41 -0
- package/dist/constants.js.map +1 -0
- package/dist/diagnostic.d.ts +34 -0
- package/dist/diagnostic.js +28 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/names.d.ts +20 -0
- package/dist/names.js +29 -0
- package/dist/names.js.map +1 -0
- package/dist/node.d.ts +45 -0
- package/dist/node.js +137 -0
- package/dist/node.js.map +1 -0
- package/dist/port.d.ts +193 -0
- package/dist/port.js +14 -0
- package/dist/port.js.map +1 -0
- package/dist/procedures.d.ts +62 -0
- package/dist/procedures.js +154 -0
- package/dist/procedures.js.map +1 -0
- package/dist/session.d.ts +123 -0
- package/dist/session.js +599 -0
- package/dist/session.js.map +1 -0
- package/dist/text.d.ts +7 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/package.json +31 -0
- package/src/artifact.ts +62 -0
- package/src/constants.ts +47 -0
- package/src/diagnostic.ts +48 -0
- package/src/errors.ts +49 -0
- package/src/index.ts +53 -0
- package/src/node.ts +154 -0
- package/src/port.ts +213 -0
- package/src/procedures.ts +169 -0
- package/src/session.ts +747 -0
- package/src/text.ts +19 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-language procedure registry for the JavaScript bindings.
|
|
3
|
+
*
|
|
4
|
+
* Runtime-neutral: the registry, `ProcedureArguments`, and the dispatcher
|
|
5
|
+
* live here. Each adapter installs the dispatcher into its shared library
|
|
6
|
+
* through its own native callback (`galley_install_*_dispatch`) and calls
|
|
7
|
+
* {@link dispatchProcedure} with the decoded hook name.
|
|
8
|
+
*
|
|
9
|
+
* Hooks receive a `ProcedureArguments` object. Tree queries use
|
|
10
|
+
* `currentNode()` plus the ordinary `Node` methods (which call the port's
|
|
11
|
+
* node accessors on the parsing session).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { INVALID_NODE } from "./constants.ts";
|
|
15
|
+
import type { Handle, FfiPort } from "./port.ts";
|
|
16
|
+
import { encodeUtf8 } from "./text.ts";
|
|
17
|
+
import { Node } from "./node.ts";
|
|
18
|
+
import type { Session } from "./session.ts";
|
|
19
|
+
|
|
20
|
+
export class ProcedureArguments {
|
|
21
|
+
readonly #args: Handle;
|
|
22
|
+
readonly #session: Session | null;
|
|
23
|
+
readonly #port: FfiPort;
|
|
24
|
+
|
|
25
|
+
constructor(args: Handle, session: Session | null, port: FfiPort) {
|
|
26
|
+
this.#args = args;
|
|
27
|
+
this.#session = session;
|
|
28
|
+
this.#port = port;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get session(): Session | null {
|
|
32
|
+
return this.#session;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
currentNode(): Node | null {
|
|
36
|
+
if (this.#session === null || this.#session.isClosed) return null;
|
|
37
|
+
const address = this.#port.procCurrentNode(this.#args);
|
|
38
|
+
if (address === INVALID_NODE) return null;
|
|
39
|
+
return new Node(this.#session, address);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setCurrentNode(node: Node | bigint): void {
|
|
43
|
+
const address = typeof node === "bigint" ? node : node.address;
|
|
44
|
+
this.#port.procSetCurrentNode(this.#args, address);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
dropSelf(): void {
|
|
48
|
+
if (this.#port.procDropSelf(this.#args) < 0) {
|
|
49
|
+
throw new Error("galley_procedure_drop_self failed");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
dropChildren(): void {
|
|
54
|
+
if (this.#port.procDropChildren(this.#args) < 0) {
|
|
55
|
+
throw new Error("galley_procedure_drop_children failed");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dropIfEmpty(): void {
|
|
60
|
+
if (this.#port.procDropIfEmpty(this.#args) < 0) {
|
|
61
|
+
throw new Error("galley_procedure_drop_if_empty failed");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
replaceWithChildren(): void {
|
|
66
|
+
if (this.#port.procReplaceWithChildren(this.#args) < 0) {
|
|
67
|
+
throw new Error("galley_procedure_replace_with_children failed");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
currentLine(): number {
|
|
72
|
+
return this.#port.procContextLine(this.#args);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
currentColumn(): number {
|
|
76
|
+
return this.#port.procContextColumn(this.#args);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Record a semantic error on the current node and return the running
|
|
81
|
+
* total. Parsing continues; a syntax-clean parse with any semantic
|
|
82
|
+
* error fails with status -12.
|
|
83
|
+
*/
|
|
84
|
+
reportSemanticError(message: string): number {
|
|
85
|
+
const status = this.#port.procReportSemanticError(this.#args, encodeUtf8(message));
|
|
86
|
+
if (status < 0) throw new Error("galley_procedure_report_semantic_error failed");
|
|
87
|
+
return status;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type HookFn = (args: ProcedureArguments) => void;
|
|
92
|
+
|
|
93
|
+
let currentSession: Session | null = null;
|
|
94
|
+
|
|
95
|
+
/** Records the Session whose parse is in flight so hooks can wrap Nodes. */
|
|
96
|
+
export function setParsingSession(session: Session | null): Session | null {
|
|
97
|
+
const previous = currentSession;
|
|
98
|
+
currentSession = session;
|
|
99
|
+
return previous;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Session of the parse currently in flight, if any. */
|
|
103
|
+
export function getParsingSession(): Session | null {
|
|
104
|
+
return currentSession;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const registry = new Map<string, HookFn>();
|
|
108
|
+
|
|
109
|
+
function isProcedureName(name: string): boolean {
|
|
110
|
+
return name === "reduction" || name.startsWith("reduction_") || name.startsWith("hook_");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Runs the registered hook for `name` (silent no-op when unregistered).
|
|
115
|
+
* Called by each adapter's native dispatch callback. Hook exceptions are
|
|
116
|
+
* logged and swallowed so a throwing hook never aborts the parse (mirrors
|
|
117
|
+
* Python's PyErr_Print behavior).
|
|
118
|
+
*/
|
|
119
|
+
export function dispatchProcedure(name: string, args: Handle, port: FfiPort): void {
|
|
120
|
+
const fn = registry.get(name);
|
|
121
|
+
if (!fn) return;
|
|
122
|
+
try {
|
|
123
|
+
if (fn.length === 0) {
|
|
124
|
+
(fn as () => void)();
|
|
125
|
+
} else {
|
|
126
|
+
(fn as HookFn)(new ProcedureArguments(args, currentSession, port));
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error(`galley procedure ${name} threw:`, err);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Installs a single procedure hook. `fn` may be
|
|
135
|
+
* `(args: ProcedureArguments)=>void` or `()=>void`.
|
|
136
|
+
* Overwrites any existing entry for `name`.
|
|
137
|
+
*/
|
|
138
|
+
export function installProcedure(name: string, fn: HookFn | (() => void)): void {
|
|
139
|
+
if (typeof name !== "string" || name.length === 0) throw new TypeError("procedure name must be non-empty string");
|
|
140
|
+
if (typeof fn !== "function") throw new TypeError("procedure must be a function");
|
|
141
|
+
registry.set(name, fn as HookFn);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Scans `module` for exported procedure hooks (`reduction`, `reduction_*`,
|
|
146
|
+
* `hook_*`) and registers each function. Returns the number installed.
|
|
147
|
+
* Mirrors `bindings/python/_galley.c:2339` `install_procedures`.
|
|
148
|
+
*/
|
|
149
|
+
export function installProcedures(module: Record<string, unknown>): number {
|
|
150
|
+
if (module === null || typeof module !== "object") throw new TypeError("module must be an object");
|
|
151
|
+
let count = 0;
|
|
152
|
+
for (const [name, value] of Object.entries(module)) {
|
|
153
|
+
if (typeof value !== "function") continue;
|
|
154
|
+
if (!isProcedureName(name)) continue;
|
|
155
|
+
registry.set(name, value as HookFn);
|
|
156
|
+
count++;
|
|
157
|
+
}
|
|
158
|
+
return count;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Removes all registered hooks; subsequent parses will be no-ops. */
|
|
162
|
+
export function clearProcedures(): void {
|
|
163
|
+
registry.clear();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Returns currently registered procedure names. */
|
|
167
|
+
export function listProcedures(): string[] {
|
|
168
|
+
return [...registry.keys()];
|
|
169
|
+
}
|