satteri 0.9.4 → 0.10.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 +11 -1
- package/dist/binding.browser.d.ts +1 -1
- package/dist/binding.browser.js +1 -1
- package/dist/binding.d.ts +1 -1
- package/dist/binding.js +1 -1
- package/dist/byte-writer.d.ts +4 -0
- package/dist/byte-writer.js +13 -4
- package/dist/child-stub.d.ts +7 -3
- package/dist/child-stub.js +18 -7
- package/dist/command-buffer.d.ts +36 -50
- package/dist/command-buffer.js +102 -49
- package/dist/compile.d.ts +80 -32
- package/dist/compile.js +295 -70
- package/dist/freeze.d.ts +2 -0
- package/dist/freeze.js +10 -0
- package/dist/generated/wire-constants.d.ts +3 -2
- package/dist/generated/wire-constants.js +4 -2
- package/dist/hast/child-stub.d.ts +2 -3
- package/dist/hast/child-stub.js +18 -11
- package/dist/hast/element-props.d.ts +1 -1
- package/dist/hast/element-props.js +14 -6
- package/dist/hast/hast-materializer.d.ts +6 -2
- package/dist/hast/hast-materializer.js +48 -134
- package/dist/hast/hast-reader.d.ts +7 -13
- package/dist/hast/hast-reader.js +76 -21
- package/dist/hast/hast-visitor.d.ts +55 -5
- package/dist/hast/hast-visitor.js +320 -154
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/lazy-child-resolver.d.ts +20 -13
- package/dist/lazy-child-resolver.js +66 -38
- package/dist/materializer-cache.d.ts +42 -0
- package/dist/materializer-cache.js +192 -0
- package/dist/mdast/child-stub.d.ts +6 -3
- package/dist/mdast/child-stub.js +71 -8
- package/dist/mdast/generated/layout.d.ts +2 -3
- package/dist/mdast/generated/layout.js +24 -28
- package/dist/mdast/generated/node-types.d.ts +4 -0
- package/dist/mdast/generated/node-types.js +25 -0
- package/dist/mdast/mdast-materializer.d.ts +12 -3
- package/dist/mdast/mdast-materializer.js +83 -78
- package/dist/mdast/mdast-reader.d.ts +15 -0
- package/dist/mdast/mdast-reader.js +55 -10
- package/dist/mdast/mdast-visitor.d.ts +79 -32
- package/dist/mdast/mdast-visitor.js +305 -122
- package/dist/op-stream.d.ts +5 -12
- package/dist/op-stream.js +6 -21
- package/dist/plugin.d.ts +36 -10
- package/dist/plugin.js +50 -2
- package/dist/string-pool.d.ts +13 -0
- package/dist/string-pool.js +91 -0
- package/dist/types.d.ts +44 -1
- package/dist/visitor-shared.d.ts +27 -4
- package/dist/visitor-shared.js +36 -10
- package/dist/wire-read.js +3 -1
- package/index.d.ts +130 -7
- package/index.js +89 -68
- package/package.json +24 -19
- package/dist/lazy-props.d.ts +0 -10
- package/dist/lazy-props.js +0 -50
- package/satteri_napi.wasi-browser.js +0 -83
- package/satteri_napi.wasi.cjs +0 -134
- package/wasi-worker-browser.mjs +0 -36
- package/wasi-worker.mjs +0 -63
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// @generated by `cargo run -p satteri-layout-codegen`. Do not edit by hand.
|
|
2
2
|
import { ru16, ru32, rstr } from "../../wire-read.js";
|
|
3
3
|
import { restorePhantomSpaces } from "../../phantom.js";
|
|
4
|
-
import { lazyGroup } from "../../lazy-props.js";
|
|
5
4
|
import { decodeMdxJsxAttr } from "../../mdx-attr.js";
|
|
6
5
|
import { decodeColumnAlign } from "../column-align.js";
|
|
7
6
|
/** Walk-wire fields per node-type tag (see Rust `write_mdast_type_data_inline`). */
|
|
@@ -57,13 +56,16 @@ const MDAST_LAYOUTS = {
|
|
|
57
56
|
{ js: "value", offset: 8, kind: "str32" },
|
|
58
57
|
],
|
|
59
58
|
28: [{ js: "value", offset: 8, kind: "str32" }],
|
|
59
|
+
38: [
|
|
60
|
+
{ js: "name", offset: 0, kind: "str16" },
|
|
61
|
+
{ js: "value", offset: 8, kind: "str32" },
|
|
62
|
+
],
|
|
60
63
|
102: [{ js: "value", offset: 0, kind: "str32", phantom: true }],
|
|
61
64
|
103: [{ js: "value", offset: 0, kind: "str32", phantom: true }],
|
|
62
65
|
104: [{ js: "value", offset: 0, kind: "str32", phantom: true }],
|
|
63
66
|
};
|
|
64
67
|
/** Materialized property names per tag (the non-skip `js` names above),
|
|
65
|
-
*
|
|
66
|
-
* Exported for the child-stub field tables. */
|
|
68
|
+
* exported for the child-stub field tables. */
|
|
67
69
|
export const MDAST_LAYOUT_KEYS = {
|
|
68
70
|
2: ["depth"],
|
|
69
71
|
7: ["value"],
|
|
@@ -81,6 +83,7 @@ export const MDAST_LAYOUT_KEYS = {
|
|
|
81
83
|
20: ["identifier", "label"],
|
|
82
84
|
27: ["meta", "value"],
|
|
83
85
|
28: ["value"],
|
|
86
|
+
38: ["name", "value"],
|
|
84
87
|
102: ["value"],
|
|
85
88
|
103: ["value"],
|
|
86
89
|
104: ["value"],
|
|
@@ -237,36 +240,29 @@ export function decodeMdastTypeData(view, buf, start, nodeType, node) {
|
|
|
237
240
|
* Attach a node's fixed `type_data` fields, materialized from the arena
|
|
238
241
|
* snapshot, driven by `MDAST_LAYOUTS`. Returns `false` for tags with no entry,
|
|
239
242
|
* so the materializer falls through to its hand-written cases (list, table,
|
|
240
|
-
* directives, MDX JSX). Fields
|
|
243
|
+
* directives, MDX JSX). Fields are eager plain stores.
|
|
241
244
|
*/
|
|
242
245
|
export function materializeMdastFields(reader, node, nodeId, nodeType) {
|
|
243
246
|
const fields = MDAST_LAYOUTS[nodeType];
|
|
244
247
|
if (fields === undefined)
|
|
245
248
|
return false;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
out[f.js] = f.values ? (f.values[b] ?? f.values[0]) : b;
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
// Short type_data (e.g. a 20-byte ReferenceData-only imageReference)
|
|
259
|
-
// yields an empty ref, mirroring the Rust decoders' bounds checks.
|
|
260
|
-
const ref = f.offset + 8 <= data.length
|
|
261
|
-
? reader.readStringRef(data, f.offset)
|
|
262
|
-
: { offset: 0, len: 0 };
|
|
263
|
-
if (f.skip)
|
|
264
|
-
continue;
|
|
265
|
-
const s = f.nullable && ref.len === 0 ? null : reader.getString(ref.offset, ref.len);
|
|
266
|
-
out[f.js] = f.phantom && typeof s === "string" ? restorePhantomSpaces(s) : s;
|
|
267
|
-
}
|
|
249
|
+
const out = node;
|
|
250
|
+
for (const f of fields) {
|
|
251
|
+
if (f.skip)
|
|
252
|
+
continue;
|
|
253
|
+
if (f.kind === "u8") {
|
|
254
|
+
// Short type_data falls back to the layout's declared default,
|
|
255
|
+
// matching the Rust walk serializer.
|
|
256
|
+
const b = reader.fieldU8(nodeId, f.offset, f.default ?? 0);
|
|
257
|
+
out[f.js] = f.values ? (f.values[b] ?? f.values[0]) : b;
|
|
268
258
|
}
|
|
269
|
-
|
|
270
|
-
|
|
259
|
+
else {
|
|
260
|
+
// Short type_data (e.g. a 20-byte ReferenceData-only imageReference)
|
|
261
|
+
// reads as empty, mirroring the Rust decoders' bounds checks.
|
|
262
|
+
const raw = reader.fieldString(nodeId, f.offset);
|
|
263
|
+
const s = f.nullable && raw === "" ? null : raw;
|
|
264
|
+
out[f.js] = f.phantom && typeof s === "string" ? restorePhantomSpaces(s) : s;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
271
267
|
return true;
|
|
272
268
|
}
|
|
@@ -38,6 +38,10 @@ export declare const NodeType: Readonly<{
|
|
|
38
38
|
readonly TextDirective: 32;
|
|
39
39
|
readonly Superscript: 33;
|
|
40
40
|
readonly Subscript: 34;
|
|
41
|
+
readonly DescriptionList: 35;
|
|
42
|
+
readonly DescriptionTerm: 36;
|
|
43
|
+
readonly DescriptionDetails: 37;
|
|
44
|
+
readonly Custom: 38;
|
|
41
45
|
readonly MdxJsxFlowElement: 100;
|
|
42
46
|
readonly MdxJsxTextElement: 101;
|
|
43
47
|
readonly MdxFlowExpression: 102;
|
|
@@ -35,6 +35,10 @@ export const TYPE_NAMES = {
|
|
|
35
35
|
32: "textDirective",
|
|
36
36
|
33: "superscript",
|
|
37
37
|
34: "subscript",
|
|
38
|
+
35: "descriptionList",
|
|
39
|
+
36: "descriptionTerm",
|
|
40
|
+
37: "descriptionDetails",
|
|
41
|
+
38: "custom",
|
|
38
42
|
100: "mdxJsxFlowElement",
|
|
39
43
|
101: "mdxJsxTextElement",
|
|
40
44
|
102: "mdxFlowExpression",
|
|
@@ -77,6 +81,10 @@ export const NAME_TO_TYPE = {
|
|
|
77
81
|
textDirective: 32,
|
|
78
82
|
superscript: 33,
|
|
79
83
|
subscript: 34,
|
|
84
|
+
descriptionList: 35,
|
|
85
|
+
descriptionTerm: 36,
|
|
86
|
+
descriptionDetails: 37,
|
|
87
|
+
custom: 38,
|
|
80
88
|
mdxJsxFlowElement: 100,
|
|
81
89
|
mdxJsxTextElement: 101,
|
|
82
90
|
mdxFlowExpression: 102,
|
|
@@ -119,6 +127,10 @@ export const NodeType = Object.freeze({
|
|
|
119
127
|
TextDirective: 32,
|
|
120
128
|
Superscript: 33,
|
|
121
129
|
Subscript: 34,
|
|
130
|
+
DescriptionList: 35,
|
|
131
|
+
DescriptionTerm: 36,
|
|
132
|
+
DescriptionDetails: 37,
|
|
133
|
+
Custom: 38,
|
|
122
134
|
MdxJsxFlowElement: 100,
|
|
123
135
|
MdxJsxTextElement: 101,
|
|
124
136
|
MdxFlowExpression: 102,
|
|
@@ -161,6 +173,10 @@ export const NodeTypeName = {
|
|
|
161
173
|
32: "TextDirective",
|
|
162
174
|
33: "Superscript",
|
|
163
175
|
34: "Subscript",
|
|
176
|
+
35: "DescriptionList",
|
|
177
|
+
36: "DescriptionTerm",
|
|
178
|
+
37: "DescriptionDetails",
|
|
179
|
+
38: "Custom",
|
|
164
180
|
100: "MdxJsxFlowElement",
|
|
165
181
|
101: "MdxJsxTextElement",
|
|
166
182
|
102: "MdxFlowExpression",
|
|
@@ -202,6 +218,10 @@ export const VISITOR_KEYS = new Set([
|
|
|
202
218
|
"textDirective",
|
|
203
219
|
"superscript",
|
|
204
220
|
"subscript",
|
|
221
|
+
"descriptionList",
|
|
222
|
+
"descriptionTerm",
|
|
223
|
+
"descriptionDetails",
|
|
224
|
+
"custom",
|
|
205
225
|
"mdxJsxFlowElement",
|
|
206
226
|
"mdxJsxTextElement",
|
|
207
227
|
"mdxFlowExpression",
|
|
@@ -245,6 +265,10 @@ export const MDAST_OPSTREAM_TYPES = {
|
|
|
245
265
|
textDirective: 32,
|
|
246
266
|
superscript: 33,
|
|
247
267
|
subscript: 34,
|
|
268
|
+
descriptionList: 35,
|
|
269
|
+
descriptionTerm: 36,
|
|
270
|
+
descriptionDetails: 37,
|
|
271
|
+
custom: 38,
|
|
248
272
|
mdxJsxFlowElement: 100,
|
|
249
273
|
mdxJsxTextElement: 101,
|
|
250
274
|
mdxFlowExpression: 102,
|
|
@@ -261,6 +285,7 @@ export const MDAST_CUSTOM_TYPES = [
|
|
|
261
285
|
"containerDirective",
|
|
262
286
|
"leafDirective",
|
|
263
287
|
"textDirective",
|
|
288
|
+
"descriptionDetails",
|
|
264
289
|
"mdxJsxFlowElement",
|
|
265
290
|
"mdxJsxTextElement",
|
|
266
291
|
];
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { Root } from "mdast";
|
|
2
|
-
import type { MdastNode } from "../types.js";
|
|
3
2
|
import type { MdastReader } from "./mdast-reader.js";
|
|
4
3
|
export declare const LEAF_TYPES: ReadonlySet<number>;
|
|
4
|
+
/** A `custom` node is a leaf when it has a non-empty `value` and no children or
|
|
5
|
+
* `data.h*`. Leafness is per node there, not per type, so the read paths ask
|
|
6
|
+
* this instead of {@link LEAF_TYPES}.
|
|
7
|
+
* @see {@link Custom} */
|
|
8
|
+
export declare function isCustomLeaf(node: {
|
|
9
|
+
readonly value?: unknown;
|
|
10
|
+
readonly data?: unknown;
|
|
11
|
+
}, childCount: number): boolean;
|
|
5
12
|
/**
|
|
6
|
-
* Materialize a single MDAST node
|
|
13
|
+
* Materialize a single MDAST node; scalars eager, `children` lazy, memoized
|
|
14
|
+
* per `(reader, id)`; `frozen` (the plugin walk path) deep-freezes so plugins
|
|
15
|
+
* cannot corrupt the shared cache.
|
|
7
16
|
*/
|
|
8
|
-
export declare
|
|
17
|
+
export declare const materializeNode: (reader: MdastReader, nodeId: number, frozen?: boolean, refs?: import("../visitor-shared.js").NodeRefs) => import("mdast").Nodes;
|
|
9
18
|
/** Materialize the full tree from root (nodeId=0). */
|
|
10
19
|
export declare function materializeMdastTree(reader: MdastReader): Root;
|
|
@@ -1,47 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { TYPE_NAMES } from "./generated/node-types.js";
|
|
1
|
+
import { NAME_TO_TYPE, TYPE_NAMES } from "./generated/node-types.js";
|
|
3
2
|
import { materializeMdastFields } from "./generated/layout.js";
|
|
3
|
+
import { createMaterializer } from "../materializer-cache.js";
|
|
4
|
+
/** Internal tag for user-defined nodes; its stored `name` field carries the
|
|
5
|
+
* author's public `type` string. */
|
|
6
|
+
const MDAST_CUSTOM = NAME_TO_TYPE.custom;
|
|
4
7
|
// Leaf node types that do NOT have children.
|
|
5
8
|
// Type 9 = `definition`; type 18 = `imageReference` — leaves per mdast spec
|
|
6
9
|
// (imageReference carries `alt` as a string, not children).
|
|
7
10
|
export const LEAF_TYPES = new Set([
|
|
8
11
|
9, 10, 13, 7, 8, 14, 3, 16, 18, 20, 25, 26, 27, 28, 102, 103, 104,
|
|
9
12
|
]);
|
|
13
|
+
/** A `custom` node is a leaf when it has a non-empty `value` and no children or
|
|
14
|
+
* `data.h*`. Leafness is per node there, not per type, so the read paths ask
|
|
15
|
+
* this instead of {@link LEAF_TYPES}.
|
|
16
|
+
* @see {@link Custom} */
|
|
17
|
+
export function isCustomLeaf(node, childCount) {
|
|
18
|
+
const { value, data } = node;
|
|
19
|
+
if (childCount !== 0 || typeof value !== "string" || value === "")
|
|
20
|
+
return false;
|
|
21
|
+
if (data === null || typeof data !== "object")
|
|
22
|
+
return true;
|
|
23
|
+
if ("hName" in data && typeof data.hName === "string")
|
|
24
|
+
return false;
|
|
25
|
+
if ("hChildren" in data && Array.isArray(data.hChildren))
|
|
26
|
+
return false;
|
|
27
|
+
if (!("hProperties" in data))
|
|
28
|
+
return true;
|
|
29
|
+
const props = data.hProperties;
|
|
30
|
+
return props === null || typeof props !== "object" || Array.isArray(props);
|
|
31
|
+
}
|
|
10
32
|
/**
|
|
11
|
-
* Add type-specific
|
|
33
|
+
* Add type-specific properties to a node object as eager plain stores.
|
|
12
34
|
*/
|
|
13
35
|
function addTypeProperties(node, reader, nodeId, nodeType) {
|
|
14
36
|
// Fixed-field types materialize from the generated layout table; the rest
|
|
15
37
|
// (variable-length / cross-field) stay in the hand-written switch.
|
|
16
|
-
if (materializeMdastFields(reader, node, nodeId, nodeType))
|
|
38
|
+
if (materializeMdastFields(reader, node, nodeId, nodeType)) {
|
|
39
|
+
// User-defined node: surface the stored `name` as the open `node.type`.
|
|
40
|
+
// Drop an empty `value` so a parent node isn't given a spurious leaf field.
|
|
41
|
+
if (nodeType === MDAST_CUSTOM) {
|
|
42
|
+
const n = node;
|
|
43
|
+
if (n.name !== undefined)
|
|
44
|
+
n.type = n.name;
|
|
45
|
+
delete n.name;
|
|
46
|
+
if (n.value === "")
|
|
47
|
+
delete n.value;
|
|
48
|
+
}
|
|
17
49
|
return;
|
|
50
|
+
}
|
|
18
51
|
switch (nodeType) {
|
|
19
52
|
case 5: {
|
|
20
53
|
// list
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
54
|
+
const d = reader.getListData(nodeId);
|
|
55
|
+
const n = node;
|
|
56
|
+
n.ordered = d.ordered;
|
|
57
|
+
n.start = d.ordered ? d.start : null;
|
|
58
|
+
n.spread = d.spread;
|
|
26
59
|
break;
|
|
27
60
|
}
|
|
28
|
-
case 6:
|
|
29
|
-
|
|
61
|
+
case 6: {
|
|
62
|
+
// listItem
|
|
63
|
+
const d = reader.getListItemData(nodeId);
|
|
64
|
+
const n = node;
|
|
65
|
+
n.spread = d.spread;
|
|
66
|
+
n.checked = d.checked;
|
|
30
67
|
break;
|
|
68
|
+
}
|
|
69
|
+
case 37: {
|
|
70
|
+
// descriptionDetails
|
|
71
|
+
const d = reader.getDescriptionDetailsData(nodeId);
|
|
72
|
+
node.spread = d.spread;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
31
75
|
case 21: // table
|
|
32
|
-
|
|
33
|
-
align: lazyProp("align", () => reader.getTableAlign(nodeId)),
|
|
34
|
-
});
|
|
76
|
+
node.align = reader.getTableAlign(nodeId);
|
|
35
77
|
break;
|
|
36
78
|
case 30: // containerDirective
|
|
37
79
|
case 31: // leafDirective
|
|
38
|
-
case 32:
|
|
39
|
-
|
|
80
|
+
case 32: {
|
|
81
|
+
// textDirective
|
|
82
|
+
const d = reader.getDirectiveData(nodeId);
|
|
83
|
+
const n = node;
|
|
84
|
+
n.name = d.name;
|
|
85
|
+
n.attributes = d.attributes;
|
|
40
86
|
break;
|
|
87
|
+
}
|
|
41
88
|
case 100: // mdxJsxFlowElement
|
|
42
|
-
case 101:
|
|
43
|
-
|
|
89
|
+
case 101: {
|
|
90
|
+
// mdxJsxTextElement
|
|
91
|
+
const d = reader.getMdxJsxElementData(nodeId);
|
|
92
|
+
const n = node;
|
|
93
|
+
n.name = d.name;
|
|
94
|
+
n.attributes = d.attributes;
|
|
44
95
|
break;
|
|
96
|
+
}
|
|
45
97
|
// Nodes with no type-specific props:
|
|
46
98
|
// root(0), paragraph(1), thematicBreak(3), blockquote(4),
|
|
47
99
|
// emphasis(11), strong(12), break(14), tableRow(22), tableCell(23), delete(24)
|
|
@@ -49,68 +101,21 @@ function addTypeProperties(node, reader, nodeId, nodeType) {
|
|
|
49
101
|
break;
|
|
50
102
|
}
|
|
51
103
|
}
|
|
104
|
+
const mdastMaterializer = createMaterializer({
|
|
105
|
+
label: "materializeNode",
|
|
106
|
+
typeNames: TYPE_NAMES,
|
|
107
|
+
hasChildren: (nodeType, node, reader, nodeId) => nodeType === MDAST_CUSTOM
|
|
108
|
+
? !isCustomLeaf(node, reader.getChildIds(nodeId).length)
|
|
109
|
+
: !LEAF_TYPES.has(nodeType),
|
|
110
|
+
populate: addTypeProperties,
|
|
111
|
+
});
|
|
52
112
|
/**
|
|
53
|
-
* Materialize a single MDAST node
|
|
113
|
+
* Materialize a single MDAST node; scalars eager, `children` lazy, memoized
|
|
114
|
+
* per `(reader, id)`; `frozen` (the plugin walk path) deep-freezes so plugins
|
|
115
|
+
* cannot corrupt the shared cache.
|
|
54
116
|
*/
|
|
55
|
-
export
|
|
56
|
-
const rawNode = reader.getNode(nodeId);
|
|
57
|
-
const nodeType = rawNode.type;
|
|
58
|
-
const typeName = TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`;
|
|
59
|
-
const node = { type: typeName };
|
|
60
|
-
if (rawNode.position !== undefined) {
|
|
61
|
-
node.position = rawNode.position;
|
|
62
|
-
}
|
|
63
|
-
// _nodeId: non-enumerable internal reference
|
|
64
|
-
Object.defineProperty(node, "_nodeId", {
|
|
65
|
-
value: nodeId,
|
|
66
|
-
writable: false,
|
|
67
|
-
configurable: true,
|
|
68
|
-
enumerable: false,
|
|
69
|
-
});
|
|
70
|
-
// Type-specific lazy properties
|
|
71
|
-
addTypeProperties(node, reader, nodeId, nodeType);
|
|
72
|
-
// Plugin-set `data` survives the visitor walk via its own getter but
|
|
73
|
-
// would be dropped when materialized from a serialized handle.
|
|
74
|
-
const rawData = reader.getNodeData(nodeId);
|
|
75
|
-
if (rawData !== null) {
|
|
76
|
-
try {
|
|
77
|
-
const parsed = JSON.parse(rawData);
|
|
78
|
-
if (parsed && typeof parsed === "object" && Object.keys(parsed).length > 0) {
|
|
79
|
-
Object.defineProperty(node, "data", {
|
|
80
|
-
value: parsed,
|
|
81
|
-
writable: true,
|
|
82
|
-
configurable: true,
|
|
83
|
-
enumerable: true,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
if (process.env.NODE_ENV !== "production") {
|
|
89
|
-
console.warn(`materializeNode: malformed node_data for nodeId=${nodeId}`, err);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// children: lazy getter (only for non-leaf nodes)
|
|
94
|
-
if (!LEAF_TYPES.has(nodeType)) {
|
|
95
|
-
Object.defineProperty(node, "children", {
|
|
96
|
-
get() {
|
|
97
|
-
const childIds = reader.getChildIds(nodeId);
|
|
98
|
-
const children = childIds.map((id) => materializeNode(reader, id));
|
|
99
|
-
Object.defineProperty(this, "children", {
|
|
100
|
-
value: children,
|
|
101
|
-
writable: true,
|
|
102
|
-
configurable: true,
|
|
103
|
-
enumerable: true,
|
|
104
|
-
});
|
|
105
|
-
return children;
|
|
106
|
-
},
|
|
107
|
-
configurable: true,
|
|
108
|
-
enumerable: true,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return node;
|
|
112
|
-
}
|
|
117
|
+
export const materializeNode = mdastMaterializer.node;
|
|
113
118
|
/** Materialize the full tree from root (nodeId=0). */
|
|
114
119
|
export function materializeMdastTree(reader) {
|
|
115
|
-
return
|
|
120
|
+
return mdastMaterializer.tree(reader, 0);
|
|
116
121
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MdastNodeRaw, BufferHeader, StringRefRaw, MdxJsxAttributeUnion } from "../types.js";
|
|
2
|
+
import { readPosition } from "../wire-read.js";
|
|
2
3
|
export { NodeType, NodeTypeName } from "./generated/node-types.js";
|
|
3
4
|
export declare class MdastReader {
|
|
4
5
|
#private;
|
|
@@ -16,11 +17,19 @@ export declare class MdastReader {
|
|
|
16
17
|
getNode(nodeId: number): MdastNodeRaw;
|
|
17
18
|
/** Fast path: read only the type byte for a node. */
|
|
18
19
|
getNodeType(nodeId: number): number;
|
|
20
|
+
getPosition(nodeId: number): ReturnType<typeof readPosition>;
|
|
19
21
|
/** Fast path: read only the parent id for a node (0xffffffff at the root). */
|
|
20
22
|
getParentId(nodeId: number): number;
|
|
21
23
|
getChildIds(nodeId: number): number[];
|
|
22
24
|
/** Push child node IDs directly onto a stack array (reverse order for depth-first). */
|
|
23
25
|
pushChildIds(nodeId: number, stack: number[]): void;
|
|
26
|
+
/** Fixed-layout field reads that touch the buffer directly. The generated
|
|
27
|
+
* decoder runs once per node, so an intermediate view or ref object here
|
|
28
|
+
* costs an allocation per field. */
|
|
29
|
+
fieldU8(nodeId: number, offset: number, fallback: number): number;
|
|
30
|
+
/** `""` when the field is absent or empty; callers map that to `null` for
|
|
31
|
+
* nullable fields, matching the Rust decoders' bounds checks. */
|
|
32
|
+
fieldString(nodeId: number, offset: number): string;
|
|
24
33
|
getTypeData(nodeId: number): Uint8Array;
|
|
25
34
|
/** Read a StringRef (offset: u32 LE, len: u32 LE) from type data. */
|
|
26
35
|
readStringRef(typeData: Uint8Array, byteOffset?: number): StringRefRaw;
|
|
@@ -46,6 +55,12 @@ export declare class MdastReader {
|
|
|
46
55
|
checked: boolean | null;
|
|
47
56
|
spread: boolean;
|
|
48
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* DescriptionDetailsData #[repr(C)]: spread(0). Valid for descriptionDetails.
|
|
60
|
+
*/
|
|
61
|
+
getDescriptionDetailsData(nodeId: number): {
|
|
62
|
+
spread: boolean;
|
|
63
|
+
};
|
|
49
64
|
/**
|
|
50
65
|
* TableData #[repr(C)]: align_count(0..4), then align_count bytes.
|
|
51
66
|
* Alignment bytes: 0=none, 1=left, 2=right, 3=center.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { restorePhantomSpaces } from "../phantom.js";
|
|
2
2
|
import { readPosition } from "../wire-read.js";
|
|
3
3
|
import { decodeColumnAlign } from "./column-align.js";
|
|
4
|
+
import { PoolOffsets } from "../string-pool.js";
|
|
4
5
|
import { NodeTypeName } from "./generated/node-types.js";
|
|
5
6
|
import { ARENA_MAGIC, KIND_MDAST, FIELD, HEADER } from "../generated/arena-layout.js";
|
|
6
7
|
export { NodeType, NodeTypeName } from "./generated/node-types.js";
|
|
@@ -9,6 +10,8 @@ export class MdastReader {
|
|
|
9
10
|
#header;
|
|
10
11
|
#textDecoder;
|
|
11
12
|
#stringPoolCache = null;
|
|
13
|
+
#poolChecked = false;
|
|
14
|
+
#poolOffsets = null;
|
|
12
15
|
constructor(buffer) {
|
|
13
16
|
if (buffer instanceof Uint8Array) {
|
|
14
17
|
this.#view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
@@ -16,7 +19,7 @@ export class MdastReader {
|
|
|
16
19
|
else {
|
|
17
20
|
this.#view = new DataView(buffer);
|
|
18
21
|
}
|
|
19
|
-
this.#textDecoder = new TextDecoder("utf-8");
|
|
22
|
+
this.#textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
20
23
|
this.#header = this.#readHeader();
|
|
21
24
|
}
|
|
22
25
|
#readHeader() {
|
|
@@ -86,9 +89,21 @@ export class MdastReader {
|
|
|
86
89
|
getString(offset, len) {
|
|
87
90
|
if (len === 0)
|
|
88
91
|
return "";
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
const pool = this.getStringPool();
|
|
93
|
+
if (!this.#poolChecked) {
|
|
94
|
+
this.#poolChecked = true;
|
|
95
|
+
const { stringPoolOffset, stringPoolLen } = this.#header;
|
|
96
|
+
const extraBytes = stringPoolLen - pool.length;
|
|
97
|
+
if (extraBytes > 0) {
|
|
98
|
+
const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
|
|
99
|
+
this.#poolOffsets = new PoolOffsets(bytes, extraBytes);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// An all-ASCII pool has byte offsets equal to its UTF-16 indices.
|
|
103
|
+
const offsets = this.#poolOffsets;
|
|
104
|
+
return offsets === null
|
|
105
|
+
? pool.substring(offset, offset + len)
|
|
106
|
+
: offsets.slice(pool, offset, len);
|
|
92
107
|
}
|
|
93
108
|
getNode(nodeId) {
|
|
94
109
|
const { nodesOffset, nodeStructSize, nodeCount } = this.#header;
|
|
@@ -116,6 +131,10 @@ export class MdastReader {
|
|
|
116
131
|
const { nodesOffset, nodeStructSize } = this.#header;
|
|
117
132
|
return this.#view.getUint8(nodesOffset + nodeId * nodeStructSize + FIELD.node_type);
|
|
118
133
|
}
|
|
134
|
+
getPosition(nodeId) {
|
|
135
|
+
const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
|
|
136
|
+
return readPosition(this.#view, base + FIELD.start_offset);
|
|
137
|
+
}
|
|
119
138
|
/** Fast path: read only the parent id for a node (0xffffffff at the root). */
|
|
120
139
|
getParentId(nodeId) {
|
|
121
140
|
const { nodesOffset, nodeStructSize } = this.#header;
|
|
@@ -148,6 +167,27 @@ export class MdastReader {
|
|
|
148
167
|
stack.push(v.getUint32(childrenOffset + (childrenStart + i) * 4, true));
|
|
149
168
|
}
|
|
150
169
|
}
|
|
170
|
+
/** Fixed-layout field reads that touch the buffer directly. The generated
|
|
171
|
+
* decoder runs once per node, so an intermediate view or ref object here
|
|
172
|
+
* costs an allocation per field. */
|
|
173
|
+
fieldU8(nodeId, offset, fallback) {
|
|
174
|
+
const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
|
|
175
|
+
const v = this.#view;
|
|
176
|
+
if (offset >= v.getUint32(base + FIELD.data_len, true))
|
|
177
|
+
return fallback;
|
|
178
|
+
const at = this.#header.typeDataOffset + v.getUint32(base + FIELD.data_offset, true);
|
|
179
|
+
return v.getUint8(at + offset);
|
|
180
|
+
}
|
|
181
|
+
/** `""` when the field is absent or empty; callers map that to `null` for
|
|
182
|
+
* nullable fields, matching the Rust decoders' bounds checks. */
|
|
183
|
+
fieldString(nodeId, offset) {
|
|
184
|
+
const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
|
|
185
|
+
const v = this.#view;
|
|
186
|
+
if (offset + 8 > v.getUint32(base + FIELD.data_len, true))
|
|
187
|
+
return "";
|
|
188
|
+
const at = this.#header.typeDataOffset + v.getUint32(base + FIELD.data_offset, true) + offset;
|
|
189
|
+
return this.getString(v.getUint32(at, true), v.getUint32(at + 4, true));
|
|
190
|
+
}
|
|
151
191
|
getTypeData(nodeId) {
|
|
152
192
|
const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
|
|
153
193
|
const v = this.#view;
|
|
@@ -159,10 +199,10 @@ export class MdastReader {
|
|
|
159
199
|
}
|
|
160
200
|
/** Read a StringRef (offset: u32 LE, len: u32 LE) from type data. */
|
|
161
201
|
readStringRef(typeData, byteOffset = 0) {
|
|
162
|
-
const
|
|
202
|
+
const at = typeData.byteOffset - this.#view.byteOffset + byteOffset;
|
|
163
203
|
return {
|
|
164
|
-
offset: view.getUint32(
|
|
165
|
-
len: view.getUint32(4, true),
|
|
204
|
+
offset: this.#view.getUint32(at, true),
|
|
205
|
+
len: this.#view.getUint32(at + 4, true),
|
|
166
206
|
};
|
|
167
207
|
}
|
|
168
208
|
/**
|
|
@@ -170,9 +210,7 @@ export class MdastReader {
|
|
|
170
210
|
* These store a single StringRef as their type data.
|
|
171
211
|
*/
|
|
172
212
|
getTextValue(nodeId) {
|
|
173
|
-
|
|
174
|
-
const ref = this.readStringRef(data);
|
|
175
|
-
return this.getString(ref.offset, ref.len);
|
|
213
|
+
return this.fieldString(nodeId, 0);
|
|
176
214
|
}
|
|
177
215
|
/**
|
|
178
216
|
* ListData #[repr(C)]: start(0..4), ordered(4), spread(5), _pad(6..8).
|
|
@@ -199,6 +237,13 @@ export class MdastReader {
|
|
|
199
237
|
spread: data[1] !== 0,
|
|
200
238
|
};
|
|
201
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* DescriptionDetailsData #[repr(C)]: spread(0). Valid for descriptionDetails.
|
|
242
|
+
*/
|
|
243
|
+
getDescriptionDetailsData(nodeId) {
|
|
244
|
+
const data = this.getTypeData(nodeId);
|
|
245
|
+
return { spread: data.length > 0 && data[0] !== 0 };
|
|
246
|
+
}
|
|
202
247
|
/**
|
|
203
248
|
* TableData #[repr(C)]: align_count(0..4), then align_count bytes.
|
|
204
249
|
* Alignment bytes: 0=none, 1=left, 2=right, 3=center.
|