@divvun/cg3 0.0.0-stage → 0.5.0
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 +152 -2
- package/dist/COPYING +674 -0
- package/dist/binding.d.ts +3 -0
- package/dist/binding.js +7 -0
- package/dist/core.d.ts +37 -0
- package/dist/core.js +210 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +65 -0
- package/dist/glue.d.ts +48 -0
- package/dist/glue.js +7 -0
- package/dist/grammar.d.ts +105 -0
- package/dist/grammar.js +67 -0
- package/dist/handle.d.ts +18 -0
- package/dist/handle.js +38 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +45 -0
- package/dist/logging.d.ts +24 -0
- package/dist/logging.js +36 -0
- package/dist/pkg/cg3_wasm.d.ts +83 -0
- package/dist/pkg/cg3_wasm.js +475 -0
- package/dist/pkg/cg3_wasm_bg.wasm +0 -0
- package/dist/pkg/cg3_wasm_bg.wasm.d.ts +15 -0
- package/dist/pkg/snippets/cg3-wasm-e617f80cd6086414/src/sink.js +19 -0
- package/package.json +28 -4
package/dist/binding.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Core } from "./core.js";
|
|
2
|
+
import * as glue from "./pkg/cg3_wasm.js";
|
|
3
|
+
// [spec:cg3:req:wasm.module+1]
|
|
4
|
+
// The package's one .wasm file ships in pkg/, beside the compiled entry
|
|
5
|
+
// module, so a URL relative to this module finds it in every runtime.
|
|
6
|
+
/** The package's module instance. Making it loads nothing. */
|
|
7
|
+
export const core = new Core(glue, new URL("./pkg/cg3_wasm_bg.wasm", import.meta.url));
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Glue, GlueObject } from "./glue.ts";
|
|
2
|
+
import { type Logger, type LogLevel } from "./logging.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Where `init()` loads the module from: a URL (a string is read as one), a
|
|
5
|
+
* `Response`, the module's bytes, or a compiled `WebAssembly.Module`.
|
|
6
|
+
*/
|
|
7
|
+
export type InitSource = string | URL | Response | BufferSource | WebAssembly.Module;
|
|
8
|
+
/**
|
|
9
|
+
* A WebAssembly module instance and the state the wrapper keeps about it.
|
|
10
|
+
* The package makes exactly one, in binding.ts, and every call into the
|
|
11
|
+
* module goes through it.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Core {
|
|
14
|
+
#private;
|
|
15
|
+
/** `defaultSource` is the package's own `.wasm` file. Nothing is loaded yet. */
|
|
16
|
+
constructor(glue: Glue, defaultSource: URL);
|
|
17
|
+
/**
|
|
18
|
+
* Loads and starts the module. Every call after the first returns the first
|
|
19
|
+
* call's promise, whatever its argument, so there is only ever one instance.
|
|
20
|
+
* A start that fails leaves no instance, and the next call tries again.
|
|
21
|
+
*/
|
|
22
|
+
init(source?: InitSource): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Runs `f` against the glue once `init()` has resolved, and turns whatever
|
|
25
|
+
* it throws into a `Cg3Error`. After a trap it throws `poisoned` without
|
|
26
|
+
* running `f`.
|
|
27
|
+
*/
|
|
28
|
+
call<T>(f: (glue: Glue) => T): T;
|
|
29
|
+
/** Sends the library's log events to `logger`, or to the console for null. */
|
|
30
|
+
setLogger(logger: Logger | null): void;
|
|
31
|
+
/** Logs events at `level` and the levels before it. The module skips the rest. */
|
|
32
|
+
setLogLevel(level: LogLevel): void;
|
|
33
|
+
/** Frees `glue` when `owner` becomes unreachable, unless `release` frees it first. */
|
|
34
|
+
adopt(owner: object, glue: GlueObject): void;
|
|
35
|
+
/** Frees `glue` at once, for `owner.free()`. Throws `poisoned` only if freeing traps. */
|
|
36
|
+
release(owner: object, glue: GlueObject): void;
|
|
37
|
+
}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { Cg3Error, fromGlue, messageOf } from "./errors.js";
|
|
10
|
+
import { consoleLogger, eventLevel, isLogLevel, levelNumber, } from "./logging.js";
|
|
11
|
+
/**
|
|
12
|
+
* A WebAssembly module instance and the state the wrapper keeps about it.
|
|
13
|
+
* The package makes exactly one, in binding.ts, and every call into the
|
|
14
|
+
* module goes through it.
|
|
15
|
+
*/
|
|
16
|
+
export class Core {
|
|
17
|
+
#glue;
|
|
18
|
+
#defaultSource;
|
|
19
|
+
#starting = null;
|
|
20
|
+
#ready = false;
|
|
21
|
+
/** Set by the first trap, and never cleared. */
|
|
22
|
+
#poison = null;
|
|
23
|
+
/** The message of a panic whose trap has not reached the wrapper yet. */
|
|
24
|
+
#panic = null;
|
|
25
|
+
#logger = consoleLogger;
|
|
26
|
+
/**
|
|
27
|
+
* Glue objects held for the life of the page once the instance is poisoned.
|
|
28
|
+
* Unreachable, wasm-bindgen's own registry would free them by entering the
|
|
29
|
+
* module, which a poisoned instance must never do.
|
|
30
|
+
*/
|
|
31
|
+
#kept = [];
|
|
32
|
+
/**
|
|
33
|
+
* Frees the glue object of a wrapper the caller dropped. Holding the glue
|
|
34
|
+
* object as the held value keeps it reachable, so wasm-bindgen's registry
|
|
35
|
+
* never frees it first.
|
|
36
|
+
*/
|
|
37
|
+
#registry = new FinalizationRegistry((glue) => this.#free(glue, false));
|
|
38
|
+
#sink = {
|
|
39
|
+
log: (level, target, message) => this.#deliver(level, target, message),
|
|
40
|
+
panic: (message) => {
|
|
41
|
+
this.#panic = message;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/** `defaultSource` is the package's own `.wasm` file. Nothing is loaded yet. */
|
|
45
|
+
constructor(glue, defaultSource) {
|
|
46
|
+
this.#glue = glue;
|
|
47
|
+
this.#defaultSource = defaultSource;
|
|
48
|
+
}
|
|
49
|
+
// [spec:cg3:req:wasm.init]
|
|
50
|
+
/**
|
|
51
|
+
* Loads and starts the module. Every call after the first returns the first
|
|
52
|
+
* call's promise, whatever its argument, so there is only ever one instance.
|
|
53
|
+
* A start that fails leaves no instance, and the next call tries again.
|
|
54
|
+
*/
|
|
55
|
+
init(source) {
|
|
56
|
+
if (this.#starting === null) {
|
|
57
|
+
const starting = this.#start(source ?? this.#defaultSource);
|
|
58
|
+
this.#starting = starting;
|
|
59
|
+
starting.catch(() => {
|
|
60
|
+
if (this.#starting === starting)
|
|
61
|
+
this.#starting = null;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return this.#starting;
|
|
65
|
+
}
|
|
66
|
+
async #start(source) {
|
|
67
|
+
try {
|
|
68
|
+
await this.#glue.default({ module_or_path: await load(source) });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throw new Cg3Error("init-failed", `cannot start @divvun/cg3: ${messageOf(error)}`, {
|
|
72
|
+
cause: error,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
this.#enter(() => this.#glue.install(this.#sink, levelNumber("warn")));
|
|
76
|
+
this.#ready = true;
|
|
77
|
+
}
|
|
78
|
+
// [spec:cg3:req:wasm.init]
|
|
79
|
+
// [spec:cg3:req:wasm.errors+1]
|
|
80
|
+
/**
|
|
81
|
+
* Runs `f` against the glue once `init()` has resolved, and turns whatever
|
|
82
|
+
* it throws into a `Cg3Error`. After a trap it throws `poisoned` without
|
|
83
|
+
* running `f`.
|
|
84
|
+
*/
|
|
85
|
+
call(f) {
|
|
86
|
+
this.#check();
|
|
87
|
+
return this.#enter(() => f(this.#glue));
|
|
88
|
+
}
|
|
89
|
+
#check() {
|
|
90
|
+
if (this.#poison !== null)
|
|
91
|
+
throw this.#poisoned();
|
|
92
|
+
if (!this.#ready) {
|
|
93
|
+
throw new Cg3Error("not-initialised", "@divvun/cg3 is not initialised: call init() and wait for it to resolve first");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
#enter(f) {
|
|
97
|
+
let result;
|
|
98
|
+
try {
|
|
99
|
+
result = f();
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw this.#translate(error);
|
|
103
|
+
}
|
|
104
|
+
// A logger that called back in can trap the module in a nested call. That
|
|
105
|
+
// poisons the instance without unwinding this call, whose result is then
|
|
106
|
+
// as untrustworthy as the memory it came from.
|
|
107
|
+
if (this.#poison !== null)
|
|
108
|
+
throw this.#poisoned();
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
#translate(error) {
|
|
112
|
+
if (error instanceof Cg3Error)
|
|
113
|
+
return error;
|
|
114
|
+
if (error instanceof WebAssembly.RuntimeError) {
|
|
115
|
+
this.#poisonWith(error);
|
|
116
|
+
return this.#poisoned();
|
|
117
|
+
}
|
|
118
|
+
return fromGlue(error) ?? error;
|
|
119
|
+
}
|
|
120
|
+
#poisonWith(trap) {
|
|
121
|
+
if (this.#poison !== null)
|
|
122
|
+
return;
|
|
123
|
+
const panic = this.#panic;
|
|
124
|
+
this.#panic = null;
|
|
125
|
+
const message = panic === null ? `the module trapped: ${trap.message}` : `the module panicked: ${panic}`;
|
|
126
|
+
this.#poison = { message, trap };
|
|
127
|
+
}
|
|
128
|
+
#poisoned() {
|
|
129
|
+
const message = this.#poison?.message ?? "the module trapped";
|
|
130
|
+
return new Cg3Error("poisoned", `${message}. Load the page or process again to recover.`, {
|
|
131
|
+
cause: this.#poison?.trap,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// [spec:cg3:req:wasm.logging]
|
|
135
|
+
/** Sends the library's log events to `logger`, or to the console for null. */
|
|
136
|
+
setLogger(logger) {
|
|
137
|
+
this.#check();
|
|
138
|
+
if (logger !== null && typeof logger !== "function") {
|
|
139
|
+
throw new Cg3Error("invalid-argument", "a logger is a function, or null for the console");
|
|
140
|
+
}
|
|
141
|
+
this.#logger = logger ?? consoleLogger;
|
|
142
|
+
}
|
|
143
|
+
/** Logs events at `level` and the levels before it. The module skips the rest. */
|
|
144
|
+
setLogLevel(level) {
|
|
145
|
+
this.#check();
|
|
146
|
+
if (!isLogLevel(level)) {
|
|
147
|
+
throw new Cg3Error("invalid-argument", `there is no log level called ${String(level)}`);
|
|
148
|
+
}
|
|
149
|
+
this.#enter(() => this.#glue.setLogLevel(levelNumber(level)));
|
|
150
|
+
}
|
|
151
|
+
// A logger that throws must not throw into the module, which is mid-call.
|
|
152
|
+
#deliver(level, target, message) {
|
|
153
|
+
try {
|
|
154
|
+
this.#logger({ level: eventLevel(level), target, message });
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error("@divvun/cg3: the logger threw:", error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// [spec:cg3:req:wasm.handle-lifetime]
|
|
161
|
+
/** Frees `glue` when `owner` becomes unreachable, unless `release` frees it first. */
|
|
162
|
+
adopt(owner, glue) {
|
|
163
|
+
this.#registry.register(owner, glue, owner);
|
|
164
|
+
}
|
|
165
|
+
/** Frees `glue` at once, for `owner.free()`. Throws `poisoned` only if freeing traps. */
|
|
166
|
+
release(owner, glue) {
|
|
167
|
+
this.#registry.unregister(owner);
|
|
168
|
+
this.#free(glue, true);
|
|
169
|
+
}
|
|
170
|
+
#free(glue, explicit) {
|
|
171
|
+
if (this.#poison !== null) {
|
|
172
|
+
this.#kept.push(glue);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
this.#enter(() => glue.free());
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// A finalizer has no caller to throw to. The next call reports the poison.
|
|
180
|
+
if (explicit)
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// [spec:cg3:req:wasm.init]
|
|
186
|
+
async function load(source) {
|
|
187
|
+
if (typeof source === "string" || source instanceof URL) {
|
|
188
|
+
const url = typeof source === "string" && URL.canParse(source) ? new URL(source) : source;
|
|
189
|
+
if (url instanceof URL && url.protocol === "file:")
|
|
190
|
+
return await readFile(url);
|
|
191
|
+
return checked(await fetch(url));
|
|
192
|
+
}
|
|
193
|
+
if (source instanceof Response)
|
|
194
|
+
return checked(source);
|
|
195
|
+
return source;
|
|
196
|
+
}
|
|
197
|
+
function checked(response) {
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
throw new Error(`${response.url || "the response"} answered HTTP ${response.status}`);
|
|
200
|
+
}
|
|
201
|
+
return response;
|
|
202
|
+
}
|
|
203
|
+
// Node's fetch cannot read file: URLs, so they are read with node:fs. The
|
|
204
|
+
// specifier is a variable so that bundlers and type checkers do not follow it:
|
|
205
|
+
// code that never passes a file URL never asks for node:fs.
|
|
206
|
+
async function readFile(url) {
|
|
207
|
+
const specifier = "node:fs/promises";
|
|
208
|
+
const fs = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ /* webpackIgnore: true */ specifier));
|
|
209
|
+
return await fs.readFile(url);
|
|
210
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every code a {@link Cg3Error} can carry. Code may test `error.code` against
|
|
3
|
+
* these; they do not change between releases.
|
|
4
|
+
*
|
|
5
|
+
* From the wrapper:
|
|
6
|
+
* - `not-initialised`: a call came before `init()` had resolved.
|
|
7
|
+
* - `init-failed`: `init()` could not load, compile or start the module.
|
|
8
|
+
* - `poisoned`: the module trapped, for instance on a panic. Its memory cannot
|
|
9
|
+
* be trusted after that, so every later call throws this without entering it.
|
|
10
|
+
* - `freed`: a call on a grammar after its `free()`.
|
|
11
|
+
*
|
|
12
|
+
* From the wrapper or the library, which `crates/cg3-wasm/src/error.rs` maps
|
|
13
|
+
* here:
|
|
14
|
+
* - `invalid-argument`: an argument or run option the package cannot use,
|
|
15
|
+
* such as bytes that are not a `Uint8Array`, or an option it does not know.
|
|
16
|
+
* - `invalid-grammar`: bytes that are not a `.cg3b` the library reads. The
|
|
17
|
+
* message is the library's.
|
|
18
|
+
* - `run-failed`: a run the library reports as failed, such as one that
|
|
19
|
+
* reaches an `EXTERNAL` rule. The message is the library's. The grammar
|
|
20
|
+
* stays usable.
|
|
21
|
+
*/
|
|
22
|
+
export declare const CG3_ERROR_CODES: readonly ["not-initialised", "init-failed", "poisoned", "freed", "invalid-argument", "invalid-grammar", "run-failed"];
|
|
23
|
+
export type Cg3ErrorCode = (typeof CG3_ERROR_CODES)[number];
|
|
24
|
+
/** An error from `@divvun/cg3`. `code` says what went wrong, in a form code can test. */
|
|
25
|
+
export declare class Cg3Error extends Error {
|
|
26
|
+
readonly code: Cg3ErrorCode;
|
|
27
|
+
constructor(code: Cg3ErrorCode, message: string, options?: ErrorOptions);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The error for an object a glue call threw, or null when it is not one. The
|
|
31
|
+
* glue throws plain objects with `code` and `message`. It only sends the codes
|
|
32
|
+
* `crates/cg3-wasm/src/error.rs` lists, which a test there checks are listed
|
|
33
|
+
* here; a code from a mismatched build reads as `run-failed` and is named in
|
|
34
|
+
* the message.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fromGlue(thrown: unknown): Cg3Error | null;
|
|
37
|
+
/** The message of anything a caller might throw. */
|
|
38
|
+
export declare function messageOf(error: unknown): string;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// [spec:cg3:req:wasm.errors+1]
|
|
2
|
+
/**
|
|
3
|
+
* Every code a {@link Cg3Error} can carry. Code may test `error.code` against
|
|
4
|
+
* these; they do not change between releases.
|
|
5
|
+
*
|
|
6
|
+
* From the wrapper:
|
|
7
|
+
* - `not-initialised`: a call came before `init()` had resolved.
|
|
8
|
+
* - `init-failed`: `init()` could not load, compile or start the module.
|
|
9
|
+
* - `poisoned`: the module trapped, for instance on a panic. Its memory cannot
|
|
10
|
+
* be trusted after that, so every later call throws this without entering it.
|
|
11
|
+
* - `freed`: a call on a grammar after its `free()`.
|
|
12
|
+
*
|
|
13
|
+
* From the wrapper or the library, which `crates/cg3-wasm/src/error.rs` maps
|
|
14
|
+
* here:
|
|
15
|
+
* - `invalid-argument`: an argument or run option the package cannot use,
|
|
16
|
+
* such as bytes that are not a `Uint8Array`, or an option it does not know.
|
|
17
|
+
* - `invalid-grammar`: bytes that are not a `.cg3b` the library reads. The
|
|
18
|
+
* message is the library's.
|
|
19
|
+
* - `run-failed`: a run the library reports as failed, such as one that
|
|
20
|
+
* reaches an `EXTERNAL` rule. The message is the library's. The grammar
|
|
21
|
+
* stays usable.
|
|
22
|
+
*/
|
|
23
|
+
export const CG3_ERROR_CODES = [
|
|
24
|
+
"not-initialised",
|
|
25
|
+
"init-failed",
|
|
26
|
+
"poisoned",
|
|
27
|
+
"freed",
|
|
28
|
+
"invalid-argument",
|
|
29
|
+
"invalid-grammar",
|
|
30
|
+
"run-failed",
|
|
31
|
+
];
|
|
32
|
+
/** An error from `@divvun/cg3`. `code` says what went wrong, in a form code can test. */
|
|
33
|
+
export class Cg3Error extends Error {
|
|
34
|
+
code;
|
|
35
|
+
constructor(code, message, options) {
|
|
36
|
+
super(message, options);
|
|
37
|
+
this.name = "Cg3Error";
|
|
38
|
+
this.code = code;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isCg3ErrorCode(code) {
|
|
42
|
+
return CG3_ERROR_CODES.includes(code);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The error for an object a glue call threw, or null when it is not one. The
|
|
46
|
+
* glue throws plain objects with `code` and `message`. It only sends the codes
|
|
47
|
+
* `crates/cg3-wasm/src/error.rs` lists, which a test there checks are listed
|
|
48
|
+
* here; a code from a mismatched build reads as `run-failed` and is named in
|
|
49
|
+
* the message.
|
|
50
|
+
*/
|
|
51
|
+
export function fromGlue(thrown) {
|
|
52
|
+
if (typeof thrown !== "object" || thrown === null || thrown instanceof Error)
|
|
53
|
+
return null;
|
|
54
|
+
const code = Reflect.get(thrown, "code");
|
|
55
|
+
const message = Reflect.get(thrown, "message");
|
|
56
|
+
if (typeof code !== "string" || typeof message !== "string")
|
|
57
|
+
return null;
|
|
58
|
+
if (isCg3ErrorCode(code))
|
|
59
|
+
return new Cg3Error(code, message);
|
|
60
|
+
return new Cg3Error("run-failed", `${message} (the module sent the unknown code ${code})`);
|
|
61
|
+
}
|
|
62
|
+
/** The message of anything a caller might throw. */
|
|
63
|
+
export function messageOf(error) {
|
|
64
|
+
return error instanceof Error ? error.message : String(error);
|
|
65
|
+
}
|
package/dist/glue.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ApplyResult, GrammarInfo } from "./grammar.ts";
|
|
2
|
+
/** What the generated init function is given. The wrapper resolves URLs itself. */
|
|
3
|
+
export type GlueInitInput = BufferSource | Response | WebAssembly.Module;
|
|
4
|
+
/**
|
|
5
|
+
* What the module calls back into, from `crates/cg3-wasm/src/sink.rs`.
|
|
6
|
+
* Levels are numbers: 0 is off, then 1 error, 2 warn, 3 info, 4 debug, 5 trace.
|
|
7
|
+
* These calls arrive while the module is running, so none of them may throw:
|
|
8
|
+
* an exception would unwind through the module and leave it half run.
|
|
9
|
+
*/
|
|
10
|
+
export interface GlueSink {
|
|
11
|
+
/** One tracing event, which the module has already checked against the level. */
|
|
12
|
+
log(level: number, target: string, message: string): void;
|
|
13
|
+
/** A panic's message. The trap that ends the call follows at once. */
|
|
14
|
+
panic(message: string): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An object the glue hands out, which owns module memory until `free()`.
|
|
18
|
+
* wasm-bindgen also frees one from its own `FinalizationRegistry` when the
|
|
19
|
+
* object becomes unreachable, which the wrapper must never let happen after a
|
|
20
|
+
* trap; see `Core`.
|
|
21
|
+
*/
|
|
22
|
+
export interface GlueObject {
|
|
23
|
+
free(): void;
|
|
24
|
+
}
|
|
25
|
+
/** A grammar the glue holds, from `crates/cg3-wasm/src/grammar.rs`. */
|
|
26
|
+
export interface GrammarGlue extends GlueObject {
|
|
27
|
+
/** A plain object the module builds. */
|
|
28
|
+
info(): GrammarInfo;
|
|
29
|
+
/**
|
|
30
|
+
* Runs the grammar on `input`. `options` is the JSON text of an
|
|
31
|
+
* `ApplyOptions` object; the module refuses a key it does not know. The
|
|
32
|
+
* result is a plain object the module builds.
|
|
33
|
+
*/
|
|
34
|
+
apply(input: string, options: string): ApplyResult;
|
|
35
|
+
}
|
|
36
|
+
/** The exports of `crates/cg3-wasm`. */
|
|
37
|
+
export interface Glue {
|
|
38
|
+
/** Instantiates the module. The generated glue keeps the instance it makes. */
|
|
39
|
+
default(options: {
|
|
40
|
+
module_or_path: GlueInitInput;
|
|
41
|
+
}): Promise<unknown>;
|
|
42
|
+
/** Starts the instance with the wrapper's sink and a log level. Called once, by `init()`. */
|
|
43
|
+
install(sink: GlueSink, level: number): void;
|
|
44
|
+
/** Sends events at `level` and above from here on, and nothing below it. */
|
|
45
|
+
setLogLevel(level: number): void;
|
|
46
|
+
version(): string;
|
|
47
|
+
readGrammar(bytes: Uint8Array): GrammarGlue;
|
|
48
|
+
}
|
package/dist/glue.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The generated wasm-bindgen glue, as the wrapper uses it. The generated
|
|
2
|
+
// module hands most results back untyped; these types give them the shapes
|
|
3
|
+
// the module builds, and tsc checks at build time that the generated module
|
|
4
|
+
// satisfies them when binding.ts hands it to Core. Declaring them here also
|
|
5
|
+
// lets the unit tests run the wrapper on a fake glue, and lets deno check the
|
|
6
|
+
// wrapper on a checkout that has no build output.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Core } from "./core.ts";
|
|
2
|
+
import type { GrammarGlue } from "./glue.ts";
|
|
3
|
+
import { Handle } from "./handle.ts";
|
|
4
|
+
/**
|
|
5
|
+
* A stream format, as vislcg3's `--in-*` and `--out-*` options name it. The
|
|
6
|
+
* binary format is left out: a string cannot carry it.
|
|
7
|
+
*/
|
|
8
|
+
export type Format = "cg" | "niceline" | "apertium" | "fst" | "plain" | "jsonl";
|
|
9
|
+
/**
|
|
10
|
+
* The options a run reads, each named after its vislcg3 long option in
|
|
11
|
+
* camelCase: `--num-windows 4` is `{ numWindows: 4 }`. An option whose value
|
|
12
|
+
* vislcg3 makes optional is `true` without one, as `--trace` is
|
|
13
|
+
* `{ trace: true }`. `input` and `output` stand for the `--in-*` and
|
|
14
|
+
* `--out-*` options. Numbers are whole numbers from 0 to 4294967295. The
|
|
15
|
+
* options a grammar sets with `CMDARGS` apply as they do in vislcg3.
|
|
16
|
+
*/
|
|
17
|
+
export interface ApplyOptions {
|
|
18
|
+
/** The input stream's format; `cg` when left out. */
|
|
19
|
+
input?: Format;
|
|
20
|
+
/** The output stream's format; `cg` when left out. */
|
|
21
|
+
output?: Format;
|
|
22
|
+
ordered?: boolean;
|
|
23
|
+
unsafe?: boolean;
|
|
24
|
+
sections?: string;
|
|
25
|
+
rules?: string;
|
|
26
|
+
rule?: string;
|
|
27
|
+
debugRules?: string;
|
|
28
|
+
verbose?: boolean | string;
|
|
29
|
+
noMappings?: boolean;
|
|
30
|
+
noCorrections?: boolean;
|
|
31
|
+
noBeforeSections?: boolean;
|
|
32
|
+
noSections?: boolean;
|
|
33
|
+
noAfterSections?: boolean;
|
|
34
|
+
trace?: boolean | string;
|
|
35
|
+
traceNameOnly?: boolean;
|
|
36
|
+
traceNoRemoved?: boolean;
|
|
37
|
+
traceEncl?: boolean;
|
|
38
|
+
deleted?: boolean;
|
|
39
|
+
singleRun?: boolean;
|
|
40
|
+
maxRuns?: number;
|
|
41
|
+
unicodeTags?: boolean;
|
|
42
|
+
uniqueTags?: boolean;
|
|
43
|
+
printIds?: boolean;
|
|
44
|
+
printDep?: boolean;
|
|
45
|
+
numWindows?: number;
|
|
46
|
+
alwaysSpan?: boolean;
|
|
47
|
+
softLimit?: number;
|
|
48
|
+
hardLimit?: number;
|
|
49
|
+
textDelimit?: boolean | string;
|
|
50
|
+
depDelimit?: boolean | string;
|
|
51
|
+
depAbsolute?: boolean;
|
|
52
|
+
depOriginal?: boolean;
|
|
53
|
+
depAllowLoops?: boolean;
|
|
54
|
+
depNoCrossing?: boolean;
|
|
55
|
+
noMagicReadings?: boolean;
|
|
56
|
+
noPassOrigin?: boolean;
|
|
57
|
+
splitMappings?: boolean;
|
|
58
|
+
showEndTags?: boolean;
|
|
59
|
+
noBreak?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** A run's output, and every warning and non-fatal error it logged, in order. */
|
|
62
|
+
export interface ApplyResult {
|
|
63
|
+
output: string;
|
|
64
|
+
warnings: string[];
|
|
65
|
+
}
|
|
66
|
+
/** What vislcg3's verbose summary reports about a loaded grammar. */
|
|
67
|
+
export interface GrammarInfo {
|
|
68
|
+
sections: number;
|
|
69
|
+
templates: number;
|
|
70
|
+
rules: number;
|
|
71
|
+
sets: number;
|
|
72
|
+
tags: number;
|
|
73
|
+
/** Rules no tag index can skip. */
|
|
74
|
+
rulesAny: number;
|
|
75
|
+
hasDependencyRules: boolean;
|
|
76
|
+
hasRelationRules: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A compiled grammar, loaded once and applied any number of times. It owns
|
|
80
|
+
* module memory: call `free()` when done with it, or declare it with `using`.
|
|
81
|
+
*/
|
|
82
|
+
export declare class Grammar extends Handle<GrammarGlue> {
|
|
83
|
+
private constructor();
|
|
84
|
+
/** What vislcg3's verbose summary reports about the grammar, as a plain object. */
|
|
85
|
+
info(): GrammarInfo;
|
|
86
|
+
/**
|
|
87
|
+
* Runs the grammar on `input`, as vislcg3 runs it on one standard input,
|
|
88
|
+
* and returns the output with every warning and non-fatal error the run
|
|
89
|
+
* logged. Each call is a run of its own. A run that fails throws
|
|
90
|
+
* `run-failed` with the library's message, and the grammar stays usable.
|
|
91
|
+
* An option it does not know throws `invalid-argument`.
|
|
92
|
+
*/
|
|
93
|
+
apply(input: string, options?: ApplyOptions): ApplyResult;
|
|
94
|
+
}
|
|
95
|
+
export interface GrammarReader {
|
|
96
|
+
/**
|
|
97
|
+
* Loads a compiled grammar from the bytes of a `.cg3b`, as `cg-comp`
|
|
98
|
+
* writes it. The bytes are copied into the module and not kept. Bytes that
|
|
99
|
+
* are not a `.cg3b` the library reads throw `invalid-grammar`, with the
|
|
100
|
+
* library's message.
|
|
101
|
+
*/
|
|
102
|
+
readGrammar(bytes: Uint8Array): Grammar;
|
|
103
|
+
}
|
|
104
|
+
/** A {@link GrammarReader} that loads into the module instance `core`. */
|
|
105
|
+
export declare function grammarReader(core: Core): GrammarReader;
|
package/dist/grammar.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Cg3Error, messageOf } from "./errors.js";
|
|
2
|
+
import { Handle } from "./handle.js";
|
|
3
|
+
let wrap;
|
|
4
|
+
/**
|
|
5
|
+
* A compiled grammar, loaded once and applied any number of times. It owns
|
|
6
|
+
* module memory: call `free()` when done with it, or declare it with `using`.
|
|
7
|
+
*/
|
|
8
|
+
export class Grammar extends Handle {
|
|
9
|
+
constructor(core, glue) {
|
|
10
|
+
super(core, glue);
|
|
11
|
+
}
|
|
12
|
+
static {
|
|
13
|
+
wrap = (core, glue) => new Grammar(core, glue);
|
|
14
|
+
}
|
|
15
|
+
// [spec:cg3:req:wasm.grammar-load+1]
|
|
16
|
+
/** What vislcg3's verbose summary reports about the grammar, as a plain object. */
|
|
17
|
+
info() {
|
|
18
|
+
return this.use((glue) => glue.info());
|
|
19
|
+
}
|
|
20
|
+
// [spec:cg3:req:wasm.parity+2]
|
|
21
|
+
// The input and options reach the module unchanged and the output comes
|
|
22
|
+
// back as the module wrote it, so a run gives what vislcg3 gives.
|
|
23
|
+
/**
|
|
24
|
+
* Runs the grammar on `input`, as vislcg3 runs it on one standard input,
|
|
25
|
+
* and returns the output with every warning and non-fatal error the run
|
|
26
|
+
* logged. Each call is a run of its own. A run that fails throws
|
|
27
|
+
* `run-failed` with the library's message, and the grammar stays usable.
|
|
28
|
+
* An option it does not know throws `invalid-argument`.
|
|
29
|
+
*/
|
|
30
|
+
apply(input, options) {
|
|
31
|
+
return this.use((glue) => glue.apply(checkedInput(input), optionsJson(options)));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function checkedInput(input) {
|
|
35
|
+
if (typeof input !== "string") {
|
|
36
|
+
throw new Cg3Error("invalid-argument", `apply takes the input as a string, not ${typeof input}`);
|
|
37
|
+
}
|
|
38
|
+
return input;
|
|
39
|
+
}
|
|
40
|
+
/** The JSON text of `options`, which the module reads and checks key by key. */
|
|
41
|
+
function optionsJson(options) {
|
|
42
|
+
if (options === undefined)
|
|
43
|
+
return "{}";
|
|
44
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
45
|
+
throw new Cg3Error("invalid-argument", "apply takes its options as an object");
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return JSON.stringify(options);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Cg3Error("invalid-argument", `the options cannot be read: ${messageOf(error)}`, {
|
|
52
|
+
cause: error,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// [spec:cg3:req:wasm.grammar-load+1]
|
|
57
|
+
/** A {@link GrammarReader} that loads into the module instance `core`. */
|
|
58
|
+
export function grammarReader(core) {
|
|
59
|
+
return {
|
|
60
|
+
readGrammar: (bytes) => core.call((glue) => {
|
|
61
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
62
|
+
throw new Cg3Error("invalid-argument", "a grammar loads from the bytes of a .cg3b, as a Uint8Array");
|
|
63
|
+
}
|
|
64
|
+
return wrap(core, glue.readGrammar(bytes));
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
}
|
package/dist/handle.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Core } from "./core.ts";
|
|
2
|
+
import type { GlueObject } from "./glue.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The base of every object that owns WebAssembly memory. `free()` and
|
|
5
|
+
* `[Symbol.dispose]()` release the memory, so a `using` declaration frees the
|
|
6
|
+
* object at the end of its block. Freeing twice does nothing, and any other
|
|
7
|
+
* call on a freed object throws `Cg3Error` with code `freed`. An object
|
|
8
|
+
* dropped without `free()` is released once it has been garbage collected.
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class Handle<G extends GlueObject> implements Disposable {
|
|
11
|
+
#private;
|
|
12
|
+
protected constructor(core: Core, glue: G);
|
|
13
|
+
/** Runs `f` on the glue object, with the module instance's call checks. */
|
|
14
|
+
protected use<T>(f: (glue: G) => T): T;
|
|
15
|
+
/** Releases the object's WebAssembly memory. Freeing twice does nothing. */
|
|
16
|
+
free(): void;
|
|
17
|
+
[Symbol.dispose](): void;
|
|
18
|
+
}
|
package/dist/handle.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Cg3Error } from "./errors.js";
|
|
2
|
+
// [spec:cg3:req:wasm.handle-lifetime]
|
|
3
|
+
/**
|
|
4
|
+
* The base of every object that owns WebAssembly memory. `free()` and
|
|
5
|
+
* `[Symbol.dispose]()` release the memory, so a `using` declaration frees the
|
|
6
|
+
* object at the end of its block. Freeing twice does nothing, and any other
|
|
7
|
+
* call on a freed object throws `Cg3Error` with code `freed`. An object
|
|
8
|
+
* dropped without `free()` is released once it has been garbage collected.
|
|
9
|
+
*/
|
|
10
|
+
export class Handle {
|
|
11
|
+
#core;
|
|
12
|
+
#glue;
|
|
13
|
+
constructor(core, glue) {
|
|
14
|
+
this.#core = core;
|
|
15
|
+
this.#glue = glue;
|
|
16
|
+
core.adopt(this, glue);
|
|
17
|
+
}
|
|
18
|
+
/** Runs `f` on the glue object, with the module instance's call checks. */
|
|
19
|
+
use(f) {
|
|
20
|
+
return this.#core.call(() => {
|
|
21
|
+
const glue = this.#glue;
|
|
22
|
+
if (glue === null)
|
|
23
|
+
throw new Cg3Error("freed", `this ${this.constructor.name} was freed`);
|
|
24
|
+
return f(glue);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Releases the object's WebAssembly memory. Freeing twice does nothing. */
|
|
28
|
+
free() {
|
|
29
|
+
const glue = this.#glue;
|
|
30
|
+
if (glue === null)
|
|
31
|
+
return;
|
|
32
|
+
this.#glue = null;
|
|
33
|
+
this.#core.release(this, glue);
|
|
34
|
+
}
|
|
35
|
+
[Symbol.dispose]() {
|
|
36
|
+
this.free();
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { InitSource } from "./core.ts";
|
|
2
|
+
import type { Logger, LogLevel } from "./logging.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Loads and starts the package's WebAssembly module. Call it once and wait
|
|
5
|
+
* for it before anything else. With no argument it loads the `.wasm` file
|
|
6
|
+
* that ships beside the package. Calling it again returns the same promise.
|
|
7
|
+
*/
|
|
8
|
+
export declare function init(source?: InitSource): Promise<void>;
|
|
9
|
+
/** The version of the cg3 library the package was built from, which is the package's own version. */
|
|
10
|
+
export declare function version(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Sends the library's log events to `logger`. `null` puts back the default,
|
|
13
|
+
* which writes each event to the console method for its level. Whatever the
|
|
14
|
+
* logger, `apply` still returns its run's warnings.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setLogger(logger: Logger | null): void;
|
|
17
|
+
/**
|
|
18
|
+
* Logs events at `level` and the more severe levels; `off` logs nothing.
|
|
19
|
+
* The default is `warn`. The module never sends the levels below it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
22
|
+
export declare const readGrammar: (bytes: Uint8Array) => import("./grammar.ts").Grammar;
|
|
23
|
+
export { Grammar } from "./grammar.ts";
|
|
24
|
+
export type { ApplyOptions, ApplyResult, Format, GrammarInfo } from "./grammar.ts";
|
|
25
|
+
export { Cg3Error } from "./errors.ts";
|
|
26
|
+
export type { Cg3ErrorCode } from "./errors.ts";
|
|
27
|
+
export type { InitSource } from "./core.ts";
|
|
28
|
+
export type { LogEvent, Logger, LogLevel } from "./logging.ts";
|