@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
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sanbus/galley-core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Runtime-neutral core for the Galley JavaScript bindings (Node, Bun, Deno). Pure TypeScript: no node:, bun:, or Deno imports. Each runtime ships a thin adapter implementing FfiPort.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./build/shim.mjs": "./build/shim.mjs",
|
|
15
|
+
"./build/builder.mjs": "./build/builder.mjs",
|
|
16
|
+
"./build/fixture.mjs": "./build/fixture.mjs"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"build",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.json",
|
|
26
|
+
"prepare": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^7.0.2"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/artifact.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { MissingArtifactError } from "./errors.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host capabilities artifact resolution needs from each adapter.
|
|
5
|
+
* Node, Bun, and wasm pass `process.env`, `path.resolve`, and an
|
|
6
|
+
* `fs.accessSync` probe; Deno passes `Deno.env.get`, the identity
|
|
7
|
+
* resolver (Deno reports the path it was given), and a `Deno.statSync`
|
|
8
|
+
* probe. Core stays runtime-neutral: no `node:`, `bun:`, or Deno imports.
|
|
9
|
+
*/
|
|
10
|
+
export interface ArtifactHost {
|
|
11
|
+
getEnv(name: string): string | undefined;
|
|
12
|
+
resolvePath(candidate: string): string;
|
|
13
|
+
existsSync(candidate: string): boolean;
|
|
14
|
+
buildHint: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Shared library filename mapping for every JavaScript adapter.
|
|
19
|
+
* One library name per platform: `lib<base>.dylib` on macOS, `<base>.dll`
|
|
20
|
+
* on Windows (no `lib` prefix), `lib<base>.so` elsewhere. The platform
|
|
21
|
+
* string comes from the host (`process.platform` under Node/Bun, where
|
|
22
|
+
* Windows reports `win32`; `Deno.build.os`, where it reports `windows`);
|
|
23
|
+
* both spellings map to the Windows name here so the adapters cannot
|
|
24
|
+
* diverge. Each adapter keeps a thin `libFileName` wrapper passing its
|
|
25
|
+
* own base name; the mapping lives here.
|
|
26
|
+
*/
|
|
27
|
+
export function artifactFileName(base: string, platform: string): string {
|
|
28
|
+
if (platform === "darwin") return `lib${base}.dylib`;
|
|
29
|
+
if (platform === "win32" || platform === "windows") return `${base}.dll`;
|
|
30
|
+
return `lib${base}.so`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Shared wasm artifact filename. WebAssembly modules are platform-neutral:
|
|
35
|
+
* always `lib<base>.wasm`. The wasm adapter keeps a thin `wasmFileName`
|
|
36
|
+
* wrapper passing its base name.
|
|
37
|
+
*/
|
|
38
|
+
export function wasmArtifactFileName(base: string): string {
|
|
39
|
+
return `lib${base}.wasm`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Shared parser-artifact resolution for every JavaScript adapter.
|
|
44
|
+
* One place is named up front — an explicit path or GALLEY_LIBRARY_PATH.
|
|
45
|
+
* Anything else is a loud error, never a search. Each adapter keeps a
|
|
46
|
+
* thin `findLibrary` wrapper passing its host capabilities and its own
|
|
47
|
+
* build hint; the decision lives here.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveArtifact(explicit: string | undefined, host: ArtifactHost): string {
|
|
50
|
+
const chosen = explicit || host.getEnv("GALLEY_LIBRARY_PATH");
|
|
51
|
+
if (!chosen) {
|
|
52
|
+
throw new MissingArtifactError(
|
|
53
|
+
"no parser artifact given; pass libraryPath or set GALLEY_LIBRARY_PATH",
|
|
54
|
+
host.buildHint,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const resolved = host.resolvePath(chosen);
|
|
58
|
+
if (!host.existsSync(resolved)) {
|
|
59
|
+
throw new MissingArtifactError(`at ${resolved}`, host.buildHint);
|
|
60
|
+
}
|
|
61
|
+
return resolved;
|
|
62
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants mirroring `bindings/c/galley.h` status and kind enumerations.
|
|
3
|
+
* These are the single source for JavaScript consumers; they must stay
|
|
4
|
+
* in sync with the C header.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const INVALID_NODE = 0xffffffffffffffffn; // 2^64-1
|
|
8
|
+
|
|
9
|
+
// Status codes (negative = failure)
|
|
10
|
+
export const STATUS_OK = 0;
|
|
11
|
+
export const STATUS_ERROR_NULL_ARGUMENT = -1;
|
|
12
|
+
export const STATUS_ERROR_SYNTAX = -2;
|
|
13
|
+
export const STATUS_ERROR_INDENTATION = -3;
|
|
14
|
+
export const STATUS_ERROR_STACK_OVERFLOW = -4;
|
|
15
|
+
export const STATUS_ERROR_AST_CAPACITY_EXCEEDED = -5;
|
|
16
|
+
export const STATUS_ERROR_UNTERMINATED_RAW_STRING = -6;
|
|
17
|
+
export const STATUS_ERROR_OUT_OF_MEMORY = -7;
|
|
18
|
+
export const STATUS_ERROR_INTERNAL = -8;
|
|
19
|
+
export const STATUS_ERROR_NO_DIAGNOSTIC = -9;
|
|
20
|
+
export const STATUS_ERROR_INVALID_NODE = -10;
|
|
21
|
+
export const STATUS_ERROR_IO = -11;
|
|
22
|
+
export const STATUS_ERROR_SEMANTIC = -12;
|
|
23
|
+
|
|
24
|
+
// Parser families
|
|
25
|
+
export const PARSER_TYPE_LL = 0;
|
|
26
|
+
export const PARSER_TYPE_LR = 1;
|
|
27
|
+
|
|
28
|
+
// Recovery modes
|
|
29
|
+
export const RECOVERY_MODE_DISABLED = 0;
|
|
30
|
+
export const RECOVERY_MODE_AUTOMATIC = 1;
|
|
31
|
+
export const RECOVERY_MODE_EXPLICIT = 2;
|
|
32
|
+
|
|
33
|
+
// Diagnostic kinds
|
|
34
|
+
export const KIND_NONE = 0;
|
|
35
|
+
export const KIND_SYNTAX = 1;
|
|
36
|
+
export const KIND_INDENTATION = 2;
|
|
37
|
+
export const KIND_SEMANTIC = 3;
|
|
38
|
+
|
|
39
|
+
// Recovery targets
|
|
40
|
+
export const RECOVERY_TARGET_NONE = 0;
|
|
41
|
+
export const RECOVERY_TARGET_LHS_VARIABLE = 1;
|
|
42
|
+
export const RECOVERY_TARGET_PRODUCTION = 2;
|
|
43
|
+
export const RECOVERY_TARGET_OCCURRENCE = 3;
|
|
44
|
+
|
|
45
|
+
// Resume sides
|
|
46
|
+
export const RESUME_BEFORE = 0;
|
|
47
|
+
export const RESUME_AFTER = 1;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only snapshot of a parse diagnostic.
|
|
3
|
+
* All `Uint8Array` fields are copies that remain valid after the next parse.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Display name for the synthetic control-byte terminals (end of input and
|
|
8
|
+
* the indentation pair), which never occur as user-typable input. Exact
|
|
9
|
+
* full-token match only; anything else returns null and the caller keeps
|
|
10
|
+
* its existing rendering.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors `tokenDisplayName` in `src/runtime/string.zig` until the general
|
|
13
|
+
* per-grammar mechanism arrives; keep the two tables in sync.
|
|
14
|
+
*/
|
|
15
|
+
export function displayTokenName(token: Uint8Array): string | null {
|
|
16
|
+
if (token.length !== 1) return null;
|
|
17
|
+
switch (token[0]) {
|
|
18
|
+
case 0x00:
|
|
19
|
+
return "End of input";
|
|
20
|
+
case 0x01:
|
|
21
|
+
return "Indent";
|
|
22
|
+
case 0x02:
|
|
23
|
+
return "Dedent";
|
|
24
|
+
default:
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Diagnostic {
|
|
30
|
+
kind: number; // KIND_*
|
|
31
|
+
line: number; // 1-based
|
|
32
|
+
column: number;
|
|
33
|
+
message: string; // plain text
|
|
34
|
+
messageAnsi: string; // with ANSI
|
|
35
|
+
unexpectedToken: Uint8Array | null; // syntax only
|
|
36
|
+
expectedTokens: Uint8Array[]; // syntax only
|
|
37
|
+
context: string[]; // innermost-first variable names, syntax only
|
|
38
|
+
syntaxErrorCount: number;
|
|
39
|
+
semanticErrorCount: number;
|
|
40
|
+
semantic: [string, string] | null; // (variable, message) for semantic errors
|
|
41
|
+
indentation: [number, number] | null; // (spaces, width) for indentation errors
|
|
42
|
+
recoveryKind: number | null; // RECOVERY_TARGET_*
|
|
43
|
+
recoveryTerminal: Uint8Array | null;
|
|
44
|
+
recoveryResume: number | null; // RESUME_*
|
|
45
|
+
recoveryLhsVariable: string | null;
|
|
46
|
+
recoveryProduction: [string, number] | null; // (variable, rhs_index)
|
|
47
|
+
recoveryOccurrence: [string, number, number, string] | null; // (parent, rhs, symbol, variable)
|
|
48
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Diagnostic } from "./diagnostic.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Failure reported by a Galley operation.
|
|
5
|
+
* Mirrors Python's `galley.Error` (code + diagnostic snapshot).
|
|
6
|
+
*/
|
|
7
|
+
export class GalleyError extends Error {
|
|
8
|
+
readonly code: number;
|
|
9
|
+
readonly diagnostic: Diagnostic | null;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
message: string,
|
|
13
|
+
code: number,
|
|
14
|
+
diagnostic: Diagnostic | null = null,
|
|
15
|
+
) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "GalleyError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.diagnostic = diagnostic;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const MISSING_ARTIFACT_CODE = "galley:missing-artifact";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The parser artifact a binding was told to load is not where it was told.
|
|
27
|
+
* Thrown by every adapter's artifact resolution instead of searching
|
|
28
|
+
* elsewhere. The universal loader catches exactly this class to try the
|
|
29
|
+
* next engine; anything else propagates loudly.
|
|
30
|
+
*/
|
|
31
|
+
export class MissingArtifactError extends Error {
|
|
32
|
+
readonly code = MISSING_ARTIFACT_CODE;
|
|
33
|
+
|
|
34
|
+
constructor(detail: string, buildHint: string) {
|
|
35
|
+
super(`galley: parser artifact not found: ${detail}.\n${buildHint}`);
|
|
36
|
+
this.name = "MissingArtifactError";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** True for missing-artifact failures even across duplicated installs. */
|
|
40
|
+
static is(error: unknown): error is MissingArtifactError {
|
|
41
|
+
if (error instanceof MissingArtifactError) return true;
|
|
42
|
+
return (
|
|
43
|
+
typeof error === "object" &&
|
|
44
|
+
error !== null &&
|
|
45
|
+
(error as { name?: unknown }).name === "MissingArtifactError" &&
|
|
46
|
+
(error as { code?: unknown }).code === MISSING_ARTIFACT_CODE
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Galley JavaScript core — runtime-neutral public surface.
|
|
3
|
+
*
|
|
4
|
+
* Each adapter package (`@sanbus/galley-node`, `@sanbus/galley-bun`, `@sanbus/galley-deno`)
|
|
5
|
+
* binds these classes to its {@link FfiPort} and re-exports them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Session, Walker } from "./session.ts";
|
|
9
|
+
import type { SessionOptions, WalkStep } from "./session.ts";
|
|
10
|
+
import { Node } from "./node.ts";
|
|
11
|
+
import { GalleyError, MissingArtifactError } from "./errors.ts";
|
|
12
|
+
import { resolveArtifact, artifactFileName, wasmArtifactFileName } from "./artifact.ts";
|
|
13
|
+
import type { ArtifactHost } from "./artifact.ts";
|
|
14
|
+
import type { Diagnostic } from "./diagnostic.ts";
|
|
15
|
+
import { displayTokenName } from "./diagnostic.ts";
|
|
16
|
+
import type { FfiPort, Handle, SessionCOptions, TreeSnapshot, WalkedStep, DispatchHandler } from "./port.ts";
|
|
17
|
+
import {
|
|
18
|
+
installProcedure,
|
|
19
|
+
installProcedures,
|
|
20
|
+
clearProcedures,
|
|
21
|
+
listProcedures,
|
|
22
|
+
ProcedureArguments,
|
|
23
|
+
dispatchProcedure,
|
|
24
|
+
setParsingSession,
|
|
25
|
+
getParsingSession,
|
|
26
|
+
} from "./procedures.ts";
|
|
27
|
+
import type { HookFn } from "./procedures.ts";
|
|
28
|
+
import { encodeUtf8, decodeUtf8, byteLengthUtf8 } from "./text.ts";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
Session,
|
|
32
|
+
Walker,
|
|
33
|
+
Node,
|
|
34
|
+
GalleyError,
|
|
35
|
+
MissingArtifactError,
|
|
36
|
+
resolveArtifact,
|
|
37
|
+
artifactFileName,
|
|
38
|
+
wasmArtifactFileName,
|
|
39
|
+
displayTokenName,
|
|
40
|
+
ProcedureArguments,
|
|
41
|
+
installProcedure,
|
|
42
|
+
installProcedures,
|
|
43
|
+
clearProcedures,
|
|
44
|
+
listProcedures,
|
|
45
|
+
dispatchProcedure,
|
|
46
|
+
setParsingSession,
|
|
47
|
+
getParsingSession,
|
|
48
|
+
encodeUtf8,
|
|
49
|
+
decodeUtf8,
|
|
50
|
+
byteLengthUtf8,
|
|
51
|
+
};
|
|
52
|
+
export type { Diagnostic, WalkStep, SessionOptions, FfiPort, Handle, SessionCOptions, TreeSnapshot, WalkedStep, DispatchHandler, HookFn, ArtifactHost };
|
|
53
|
+
export * from "./constants.ts";
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Session } from "./session.ts";
|
|
2
|
+
import { decodeUtf8 } from "./text.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Session-bound handle for a node in the non-relocating AST storage.
|
|
6
|
+
* Mirrors Python's `galley.Node`: keeps a strong reference to its Session
|
|
7
|
+
* and raises after the session is closed.
|
|
8
|
+
*/
|
|
9
|
+
export class Node {
|
|
10
|
+
readonly #session: Session;
|
|
11
|
+
readonly #address: bigint;
|
|
12
|
+
|
|
13
|
+
constructor(session: Session, address: bigint | number) {
|
|
14
|
+
this.#session = session;
|
|
15
|
+
this.#address = typeof address === "bigint" ? address : BigInt(address);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Raw address (stable index in the session's node storage). */
|
|
19
|
+
get address(): bigint {
|
|
20
|
+
return this.#address;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Owning session. */
|
|
24
|
+
get session(): Session {
|
|
25
|
+
return this.#session;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private ensureAlive(): void {
|
|
29
|
+
if (this.#session.isClosed) {
|
|
30
|
+
throw new Error("node's session is closed");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Tuple of direct children, from first to last (empty when leaf). */
|
|
35
|
+
children(): Node[] {
|
|
36
|
+
this.ensureAlive();
|
|
37
|
+
return this.#session.children(this);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Text bytes of this node, or null for invalid node. */
|
|
41
|
+
text(): Uint8Array | null {
|
|
42
|
+
this.ensureAlive();
|
|
43
|
+
return this.#session.text(this);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Symbol name bytes as string, or null for invalid node. Terminal-only nodes → "". */
|
|
47
|
+
symbolName(): string | null {
|
|
48
|
+
this.ensureAlive();
|
|
49
|
+
const bytes = this.#session.symbolNameBytes(this);
|
|
50
|
+
if (bytes === null) return null;
|
|
51
|
+
return decodeUtf8(bytes);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Raw symbol name bytes (Uint8Array) or null. */
|
|
55
|
+
symbolNameBytes(): Uint8Array | null {
|
|
56
|
+
this.ensureAlive();
|
|
57
|
+
return this.#session.symbolNameBytes(this);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** (start, length) byte span, or null. */
|
|
61
|
+
span(): [bigint, bigint] | null {
|
|
62
|
+
this.ensureAlive();
|
|
63
|
+
return this.#session.span(this);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 1-based (line, column) of first byte, or null. */
|
|
67
|
+
lineColumn(): [number, number] | null {
|
|
68
|
+
this.ensureAlive();
|
|
69
|
+
return this.#session.lineColumn(this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Parent node, or null for root. */
|
|
73
|
+
parent(): Node | null {
|
|
74
|
+
this.ensureAlive();
|
|
75
|
+
return this.#session.parent(this);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
nextSibling(): Node | null {
|
|
79
|
+
this.ensureAlive();
|
|
80
|
+
return this.#session.nextSibling(this);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
priorSibling(): Node | null {
|
|
84
|
+
this.ensureAlive();
|
|
85
|
+
return this.#session.priorSibling(this);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
firstChild(): Node | null {
|
|
89
|
+
this.ensureAlive();
|
|
90
|
+
return this.#session.firstChild(this);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
lastChild(): Node | null {
|
|
94
|
+
this.ensureAlive();
|
|
95
|
+
return this.#session.lastChild(this);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
cleanChildren(): Node | null {
|
|
99
|
+
this.ensureAlive();
|
|
100
|
+
return this.#session.cleanChildren(this);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
appendChildren(chain: Node | bigint): void {
|
|
104
|
+
this.ensureAlive();
|
|
105
|
+
this.#session.appendChildren(this, chain);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Number of direct children. */
|
|
109
|
+
get length(): number {
|
|
110
|
+
this.ensureAlive();
|
|
111
|
+
return this.#session.childCount(this);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Child at index (negative indices supported). */
|
|
115
|
+
at(index: number): Node {
|
|
116
|
+
this.ensureAlive();
|
|
117
|
+
const count = this.length;
|
|
118
|
+
let i = index;
|
|
119
|
+
if (i < 0) i += count;
|
|
120
|
+
if (i < 0 || i >= count) throw new RangeError(`node index ${index} out of range (0..${count - 1})`);
|
|
121
|
+
const arr = this.children();
|
|
122
|
+
return arr[i];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
*[Symbol.iterator](): Iterator<Node> {
|
|
126
|
+
this.ensureAlive();
|
|
127
|
+
for (const child of this.children()) yield child;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Raw address for `Number(node)` / `BigInt(node)`. */
|
|
131
|
+
valueOf(): bigint {
|
|
132
|
+
return this.#address;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
toString(): string {
|
|
136
|
+
return `Node(${this.#address.toString()})`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
equals(other: unknown): boolean {
|
|
140
|
+
if (other instanceof Node) {
|
|
141
|
+
return this.#address === other.#address && this.#session === other.#session;
|
|
142
|
+
}
|
|
143
|
+
if (typeof other === "bigint") return this.#address === other;
|
|
144
|
+
if (typeof other === "number") return this.#address === BigInt(other);
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// allow `Number(node)` and `+node`
|
|
149
|
+
[Symbol.toPrimitive](hint: string): bigint | string | number {
|
|
150
|
+
if (hint === "number") return Number(this.#address);
|
|
151
|
+
if (hint === "string") return this.toString();
|
|
152
|
+
return this.#address;
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/port.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral FFI port: the single seam between the runtime-neutral core
|
|
3
|
+
* (`session.ts`, `node.ts`, `procedures.ts`) and each runtime adapter
|
|
4
|
+
* (Node/koffi, Bun/`bun:ffi`, Deno/`Deno.dlopen`).
|
|
5
|
+
*
|
|
6
|
+
* The port mirrors `bindings/c/galley.h`, but with structured returns
|
|
7
|
+
* instead of C out-parameters: adapters own all memory copying (bytes are
|
|
8
|
+
* already-copied `Uint8Array`s here, valid after the next parse) and all
|
|
9
|
+
* integer normalization (addresses are `bigint`, counts and statuses are
|
|
10
|
+
* `number`). Opaque native pointers (sessions, walkers, procedure args)
|
|
11
|
+
* cross the seam as `Handle` and are never inspected by the core.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type Handle = unknown;
|
|
15
|
+
|
|
16
|
+
/** `GalleyCOptions` fields as plain data; null selects library defaults. */
|
|
17
|
+
export interface SessionCOptions {
|
|
18
|
+
maxErrors: number;
|
|
19
|
+
recoveryWindow: number;
|
|
20
|
+
stackOverflowRecovery: number;
|
|
21
|
+
syntaxErrorStackDepth: number;
|
|
22
|
+
verbosity: number;
|
|
23
|
+
astPreallocationRatio: number;
|
|
24
|
+
astPreallocationCap: bigint;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One pre-order walker step. */
|
|
28
|
+
export interface WalkedStep {
|
|
29
|
+
node: bigint;
|
|
30
|
+
depth: number;
|
|
31
|
+
isSemanticError: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Flat bulk read of the most recent successful parse, one slot per node
|
|
36
|
+
* address. `parent` holds `INVALID_NODE` for the root, `firstChild`/`next`
|
|
37
|
+
* hold `INVALID_NODE` where the link does not exist, `variable` holds -1
|
|
38
|
+
* for nodes without a variable, and `spanStart`/`spanLen` are byte
|
|
39
|
+
* offsets into the parsed input. Parent, firstChild, and next alone
|
|
40
|
+
* describe the whole tree with no further calls.
|
|
41
|
+
*/
|
|
42
|
+
export interface TreeSnapshot {
|
|
43
|
+
count: number;
|
|
44
|
+
parent: BigUint64Array;
|
|
45
|
+
firstChild: BigUint64Array;
|
|
46
|
+
next: BigUint64Array;
|
|
47
|
+
childCount: Uint32Array;
|
|
48
|
+
variable: BigInt64Array;
|
|
49
|
+
spanStart: BigUint64Array;
|
|
50
|
+
spanLen: BigUint64Array;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Native dispatch callback installed by the adapter; receives a decoded hook name. */
|
|
54
|
+
export type DispatchHandler = (name: string, args: Handle) => void;
|
|
55
|
+
|
|
56
|
+
export interface FfiPort {
|
|
57
|
+
// -- module-level queries (mirror galley.h) --------------------------
|
|
58
|
+
version(): string;
|
|
59
|
+
parserType(): number;
|
|
60
|
+
errorRecoveryMode(): number;
|
|
61
|
+
hasAst(): boolean;
|
|
62
|
+
hasProcedures(): boolean;
|
|
63
|
+
allowsNoAstTreeProcedures(): boolean;
|
|
64
|
+
sourceRetentionEnabled(): boolean;
|
|
65
|
+
hasPositionTracking(): boolean;
|
|
66
|
+
hasInputStreaming(): boolean;
|
|
67
|
+
usesVerbatim(): boolean;
|
|
68
|
+
stackOverflowRecoveryAvailable(): boolean;
|
|
69
|
+
symbolCount(): number;
|
|
70
|
+
variableCount(): number;
|
|
71
|
+
statusString(status: number): string | null;
|
|
72
|
+
|
|
73
|
+
// -- sessions ---------------------------------------------------------
|
|
74
|
+
/** Null handle on initialization failure (most commonly allocation failure). */
|
|
75
|
+
createSession(options: SessionCOptions | null): Handle;
|
|
76
|
+
destroySession(handle: Handle): void;
|
|
77
|
+
/** Negative status on failure. */
|
|
78
|
+
setMessageOverride(handle: Handle, name: Uint8Array, message: Uint8Array): number;
|
|
79
|
+
|
|
80
|
+
// -- parsing ----------------------------------------------------------
|
|
81
|
+
/** Bytes parsed, or a negative status code. */
|
|
82
|
+
parse(handle: Handle, data: Uint8Array): number;
|
|
83
|
+
/** Bytes parsed, or a negative status code. */
|
|
84
|
+
parseFile(handle: Handle, path: string): number;
|
|
85
|
+
/** End position of the most recent successful parse; null on failure. */
|
|
86
|
+
lastPosition(handle: Handle): [number, number] | null;
|
|
87
|
+
|
|
88
|
+
// -- arena and navigation ----------------------------------------------
|
|
89
|
+
nodeCount(handle: Handle): number;
|
|
90
|
+
/** Negative status on failure (e.g. capacity exceeded). */
|
|
91
|
+
reserveNodes(handle: Handle, capacity: bigint): number;
|
|
92
|
+
nodeCapacity(handle: Handle): number;
|
|
93
|
+
rootNode(handle: Handle): bigint;
|
|
94
|
+
nodeValid(handle: Handle, node: bigint): boolean;
|
|
95
|
+
childCount(handle: Handle, node: bigint): number;
|
|
96
|
+
firstChild(handle: Handle, node: bigint): bigint;
|
|
97
|
+
lastChild(handle: Handle, node: bigint): bigint;
|
|
98
|
+
nextSibling(handle: Handle, node: bigint): bigint;
|
|
99
|
+
priorSibling(handle: Handle, node: bigint): bigint;
|
|
100
|
+
parent(handle: Handle, node: bigint): bigint;
|
|
101
|
+
/** Flat bulk read of the most recent successful parse (see `TreeSnapshot`). */
|
|
102
|
+
treeSnapshot(handle: Handle): TreeSnapshot;
|
|
103
|
+
|
|
104
|
+
// -- walker ------------------------------------------------------------
|
|
105
|
+
/** Null without AST construction or on invalid arguments. */
|
|
106
|
+
walkerCreate(handle: Handle, node: bigint, skipSemanticErrors: boolean): Handle | null;
|
|
107
|
+
/** Null when the walk is done. */
|
|
108
|
+
walkerNext(walker: Handle): WalkedStep | null;
|
|
109
|
+
walkerSkipChildren(walker: Handle): void;
|
|
110
|
+
walkerDestroy(walker: Handle): void;
|
|
111
|
+
|
|
112
|
+
// -- node accessors (null on invalid node) ------------------------------
|
|
113
|
+
nodeSymbolName(handle: Handle, node: bigint): Uint8Array | null;
|
|
114
|
+
nodeText(handle: Handle, node: bigint): Uint8Array | null;
|
|
115
|
+
nodeSpan(handle: Handle, node: bigint): [bigint, bigint] | null;
|
|
116
|
+
nodeLineColumn(handle: Handle, node: bigint): [number, number] | null;
|
|
117
|
+
/** Raw variable index; -1 when the node has no variable. */
|
|
118
|
+
nodeVariableIndex(handle: Handle, node: bigint): number;
|
|
119
|
+
symbolNameAt(handle: Handle, index: number): Uint8Array | null;
|
|
120
|
+
symbolIsTerminal(handle: Handle, index: number): boolean;
|
|
121
|
+
variableNameAt(handle: Handle, index: number): Uint8Array | null;
|
|
122
|
+
|
|
123
|
+
// -- diagnostics ---------------------------------------------------------
|
|
124
|
+
hasDiagnostic(handle: Handle): boolean;
|
|
125
|
+
diagnosticKind(handle: Handle): number;
|
|
126
|
+
diagnosticMessage(handle: Handle): string | null;
|
|
127
|
+
diagnosticMessageAnsi(handle: Handle): string | null;
|
|
128
|
+
diagnosticPosition(handle: Handle): [number, number] | null;
|
|
129
|
+
diagnosticUnexpectedToken(handle: Handle): Uint8Array | null;
|
|
130
|
+
diagnosticExpectedCount(handle: Handle): number;
|
|
131
|
+
diagnosticExpectedAt(handle: Handle, index: number): Uint8Array | null;
|
|
132
|
+
diagnosticContextCount(handle: Handle): number;
|
|
133
|
+
diagnosticContextAt(handle: Handle, index: number): Uint8Array | null;
|
|
134
|
+
syntaxErrorCount(handle: Handle): number;
|
|
135
|
+
semanticErrorCount(handle: Handle): number;
|
|
136
|
+
/** (variable, message); null when there is no semantic diagnostic. */
|
|
137
|
+
diagnosticSemantic(handle: Handle): [string, string] | null;
|
|
138
|
+
/** (spaces, width); null when not an indentation diagnostic. */
|
|
139
|
+
diagnosticIndentation(handle: Handle): [number, number] | null;
|
|
140
|
+
|
|
141
|
+
// -- recovery, current diagnostic ------------------------------------------
|
|
142
|
+
diagnosticRecoveryKind(handle: Handle): number;
|
|
143
|
+
diagnosticRecoveryTerminal(handle: Handle): Uint8Array | null;
|
|
144
|
+
diagnosticRecoveryResume(handle: Handle): number | null;
|
|
145
|
+
diagnosticRecoveryLhsVariable(handle: Handle): string | null;
|
|
146
|
+
diagnosticRecoveryProduction(handle: Handle): [string, number] | null;
|
|
147
|
+
diagnosticRecoveryOccurrence(handle: Handle): [string, number, number, string] | null;
|
|
148
|
+
|
|
149
|
+
// -- recovery, recorded diagnostics ------------------------------------------
|
|
150
|
+
recordedDiagnosticCount(handle: Handle): number;
|
|
151
|
+
recordedDiagnosticKind(handle: Handle, diagIndex: number): number;
|
|
152
|
+
recordedDiagnosticPosition(handle: Handle, diagIndex: number): [number, number] | null;
|
|
153
|
+
recordedUnexpectedToken(handle: Handle, diagIndex: number): Uint8Array | null;
|
|
154
|
+
recordedDiagnosticMessage(handle: Handle, diagIndex: number): string | null;
|
|
155
|
+
recordedIndentation(handle: Handle, diagIndex: number): [number, number] | null;
|
|
156
|
+
recordedSemantic(handle: Handle, diagIndex: number): [string, string] | null;
|
|
157
|
+
recordedExpectedCount(handle: Handle, diagIndex: number): number;
|
|
158
|
+
recordedExpectedToken(handle: Handle, diagIndex: number, tokenIndex: number): Uint8Array | null;
|
|
159
|
+
recordedContextCount(handle: Handle, diagIndex: number): number;
|
|
160
|
+
recordedContextName(handle: Handle, diagIndex: number, contextIndex: number): Uint8Array | null;
|
|
161
|
+
recordedRecoveryKind(handle: Handle, diagIndex: number): number;
|
|
162
|
+
recordedRecoveryTerminal(handle: Handle, diagIndex: number): Uint8Array | null;
|
|
163
|
+
recordedRecoveryResume(handle: Handle, diagIndex: number): number | null;
|
|
164
|
+
recordedRecoveryLhsVariable(handle: Handle, diagIndex: number): string | null;
|
|
165
|
+
recordedRecoveryProduction(handle: Handle, diagIndex: number): [string, number] | null;
|
|
166
|
+
recordedRecoveryOccurrence(
|
|
167
|
+
handle: Handle,
|
|
168
|
+
diagIndex: number,
|
|
169
|
+
): [string, number, number, string] | null;
|
|
170
|
+
|
|
171
|
+
// -- tree editing ----------------------------------------------------------
|
|
172
|
+
treeAppendChildren(handle: Handle, parent: bigint, first: bigint): number;
|
|
173
|
+
treeInsertBefore(handle: Handle, target: bigint, first: bigint): number;
|
|
174
|
+
treeInsertAfter(handle: Handle, target: bigint, first: bigint): number;
|
|
175
|
+
treeRemoveSiblings(handle: Handle, node: bigint, count: number): { status: number; head: bigint };
|
|
176
|
+
treeRemoveSelf(handle: Handle, node: bigint): { status: number; head: bigint };
|
|
177
|
+
treePromoteChildrenOverWrapper(handle: Handle, wrapper: bigint): { status: number; head: bigint };
|
|
178
|
+
treeCleanChildren(handle: Handle, node: bigint): { status: number; head: bigint };
|
|
179
|
+
treeUnlinkWrapper(handle: Handle, wrapper: bigint): number;
|
|
180
|
+
treeInsertChildrenAt(handle: Handle, parent: bigint, index: number, first: bigint): number;
|
|
181
|
+
treeRemoveChildrenAt(
|
|
182
|
+
handle: Handle,
|
|
183
|
+
parent: bigint,
|
|
184
|
+
index: number,
|
|
185
|
+
count: number,
|
|
186
|
+
): { status: number; head: bigint };
|
|
187
|
+
|
|
188
|
+
// -- procedure hooks (parse-time state) ---------------------------------------
|
|
189
|
+
procCurrentNode(args: Handle): bigint;
|
|
190
|
+
procSetCurrentNode(args: Handle, node: bigint): void;
|
|
191
|
+
procDropSelf(args: Handle): number;
|
|
192
|
+
procDropChildren(args: Handle): number;
|
|
193
|
+
procDropIfEmpty(args: Handle): number;
|
|
194
|
+
procReplaceWithChildren(args: Handle): number;
|
|
195
|
+
procContextLine(args: Handle): number;
|
|
196
|
+
procContextColumn(args: Handle): number;
|
|
197
|
+
/** Running semantic-error total, or a negative status code. */
|
|
198
|
+
procReportSemanticError(args: Handle, message: Uint8Array): number;
|
|
199
|
+
/**
|
|
200
|
+
* Enables exactly `names` in the native procedure gates before a parse
|
|
201
|
+
* (selective dispatch): the adapter clears all gates, then enables each
|
|
202
|
+
* name. Missing symbols (C-procedure or stale libraries) are no-ops.
|
|
203
|
+
*/
|
|
204
|
+
syncProcedures(names: string[]): void;
|
|
205
|
+
/**
|
|
206
|
+
* Hook names in integer-ID order for the ID dispatch path. Queried once
|
|
207
|
+
* from the library (`galley_js_procedure_count` /
|
|
208
|
+
* `galley_js_procedure_name_ptr` / `galley_js_procedure_name_len`) and
|
|
209
|
+
* cached; empty when the library predates the query exports (C-procedure
|
|
210
|
+
* or stale libraries use the name-carrying dispatch instead).
|
|
211
|
+
*/
|
|
212
|
+
procedureNames(): string[];
|
|
213
|
+
}
|