@naeemo/capnp 0.4.0 → 0.5.2
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/dist/cli.js +2187 -305
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1362 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1311 -25
- package/dist/index.js.map +1 -1
- package/dist/rpc-connection-C3-uEtpd.js +2158 -0
- package/dist/rpc-connection-C3-uEtpd.js.map +1 -0
- package/dist/rpc-connection-CDzawjwJ.js +3 -0
- package/dist/rpc-connection-_eHtWsk2.js +2296 -0
- package/dist/rpc-connection-_eHtWsk2.js.map +1 -0
- package/dist/rpc-connection-jIPnPyT6.js +3 -0
- package/package.json +39 -27
- package/dist/codegen/cli-v3.js +0 -1857
- package/dist/codegen/cli-v3.js.map +0 -1
- package/dist/rpc-connection-BKWQQ7f9.js +0 -960
- package/dist/rpc-connection-BKWQQ7f9.js.map +0 -1
- package/dist/rpc-connection-C2C1wyga.js +0 -3
- package/dist/rpc-connection-Dz3rYT1P.js +0 -870
- package/dist/rpc-connection-Dz3rYT1P.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,328 +1,1751 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
#!/usr/bin/env node
|
|
3
|
-
import {
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { basename, dirname, join } from "node:path";
|
|
4
7
|
import { parseArgs } from "node:util";
|
|
5
8
|
|
|
6
|
-
//#region src/codegen/
|
|
9
|
+
//#region src/codegen/generator.ts
|
|
10
|
+
const DEFAULT_OPTIONS = { runtimeImportPath: "@naeemo/capnp" };
|
|
7
11
|
/**
|
|
8
|
-
*
|
|
12
|
+
* 从 CodeGeneratorRequest 生成 TypeScript 代码
|
|
9
13
|
*/
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
function generateFromRequest(request, options) {
|
|
15
|
+
const opts = {
|
|
16
|
+
...DEFAULT_OPTIONS,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
const files = /* @__PURE__ */ new Map();
|
|
20
|
+
const nodes = request.nodes;
|
|
21
|
+
const requestedFiles = request.requestedFiles;
|
|
22
|
+
for (const file of requestedFiles) {
|
|
23
|
+
const filename = file.filename.replace(/\.capnp$/, ".ts");
|
|
24
|
+
const code = generateFile(file.id, nodes, opts);
|
|
25
|
+
files.set(filename, code);
|
|
26
|
+
}
|
|
27
|
+
return files;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 生成单个文件的 TypeScript 代码
|
|
31
|
+
*/
|
|
32
|
+
function generateFile(fileId, allNodes, options) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
lines.push("// Generated by @naeemo/capnp");
|
|
35
|
+
lines.push("// DO NOT EDIT MANUALLY");
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push(`import { MessageReader, MessageBuilder, StructReader, StructBuilder, createUnionReader, createUnionBuilder } from "${options.runtimeImportPath}";`);
|
|
38
|
+
lines.push("");
|
|
39
|
+
lines.push("// XOR helpers for default value encoding");
|
|
40
|
+
lines.push("function xorFloat32(a: number, b: number): number {");
|
|
41
|
+
lines.push(" const view = new DataView(new ArrayBuffer(4));");
|
|
42
|
+
lines.push(" view.setFloat32(0, a, true);");
|
|
43
|
+
lines.push(" const aBits = view.getUint32(0, true);");
|
|
44
|
+
lines.push(" view.setFloat32(0, b, true);");
|
|
45
|
+
lines.push(" const bBits = view.getUint32(0, true);");
|
|
46
|
+
lines.push(" view.setUint32(0, aBits ^ bBits, true);");
|
|
47
|
+
lines.push(" return view.getFloat32(0, true);");
|
|
13
48
|
lines.push("}");
|
|
14
49
|
lines.push("");
|
|
50
|
+
lines.push("function xorFloat64(a: number, b: number): number {");
|
|
51
|
+
lines.push(" const view = new DataView(new ArrayBuffer(8));");
|
|
52
|
+
lines.push(" view.setFloat64(0, a, true);");
|
|
53
|
+
lines.push(" const aBits = view.getBigUint64(0, true);");
|
|
54
|
+
lines.push(" view.setFloat64(0, b, true);");
|
|
55
|
+
lines.push(" const bBits = view.getBigUint64(0, true);");
|
|
56
|
+
lines.push(" view.setBigUint64(0, aBits ^ bBits, true);");
|
|
57
|
+
lines.push(" return view.getFloat64(0, true);");
|
|
58
|
+
lines.push("}");
|
|
59
|
+
lines.push("");
|
|
60
|
+
let filePrefix = "";
|
|
61
|
+
try {
|
|
62
|
+
filePrefix = allNodes.find((n) => n.id === fileId)?.displayName || "";
|
|
63
|
+
} catch (_e) {}
|
|
64
|
+
const fileNodes = allNodes.filter((n) => {
|
|
65
|
+
try {
|
|
66
|
+
if (n.scopeId === fileId) return true;
|
|
67
|
+
if (n.scopeId === 0n && filePrefix && n.displayName.startsWith(`${filePrefix}:`)) return n.displayName.substring(filePrefix.length + 1).includes("$");
|
|
68
|
+
} catch (_e) {}
|
|
69
|
+
return false;
|
|
70
|
+
});
|
|
71
|
+
for (const node of fileNodes) {
|
|
72
|
+
if (node.isStruct) lines.push(generateStruct(node, allNodes));
|
|
73
|
+
else if (node.isEnum) lines.push(generateEnum(node));
|
|
74
|
+
else if (node.isInterface) lines.push(generateInterface(node, allNodes));
|
|
75
|
+
lines.push("");
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
15
78
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
/**
|
|
80
|
+
* 生成 Struct 的 TypeScript 代码
|
|
81
|
+
*/
|
|
82
|
+
function generateStruct(node, allNodes) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
const structName = getShortName(node.displayName);
|
|
85
|
+
let unionGroups = [];
|
|
86
|
+
let regularFields = [];
|
|
87
|
+
let groupFields = [];
|
|
88
|
+
try {
|
|
89
|
+
const analysis = analyzeFields(node, allNodes);
|
|
90
|
+
unionGroups = analysis.unionGroups;
|
|
91
|
+
regularFields = analysis.regularFields;
|
|
92
|
+
groupFields = analysis.groupFields;
|
|
93
|
+
} catch (_e) {
|
|
94
|
+
lines.push(`export interface ${structName} {`);
|
|
95
|
+
lines.push(" // Note: Could not parse struct fields");
|
|
96
|
+
lines.push("}");
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push(`export class ${structName}Reader {`);
|
|
99
|
+
lines.push(" constructor(private reader: StructReader) {}");
|
|
100
|
+
lines.push("}");
|
|
101
|
+
lines.push("");
|
|
102
|
+
lines.push(`export class ${structName}Builder {`);
|
|
103
|
+
lines.push(" constructor(private builder: StructBuilder) {}");
|
|
104
|
+
lines.push("}");
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
}
|
|
107
|
+
lines.push(`export interface ${structName} {`);
|
|
108
|
+
for (const field of regularFields) {
|
|
109
|
+
if (!field.isSlot) continue;
|
|
110
|
+
const tsType = getTypeScriptType(field.slotType, allNodes);
|
|
111
|
+
lines.push(` ${field.name}: ${tsType};`);
|
|
112
|
+
}
|
|
113
|
+
for (const { field: groupField, groupNode } of groupFields) {
|
|
114
|
+
const groupName = groupField.name;
|
|
115
|
+
try {
|
|
116
|
+
for (const field of groupNode.structFields) {
|
|
117
|
+
if (!field.isSlot) continue;
|
|
118
|
+
const tsType = getTypeScriptType(field.slotType, allNodes);
|
|
119
|
+
lines.push(` ${groupName}${capitalize(field.name)}: ${tsType};`);
|
|
120
|
+
}
|
|
121
|
+
} catch (_e) {}
|
|
122
|
+
}
|
|
123
|
+
for (const [groupIndex, group] of unionGroups.entries()) {
|
|
124
|
+
const unionName = group.fields.length > 0 ? `${capitalize(group.fields[0].name)}Union` : `Union${groupIndex}`;
|
|
125
|
+
lines.push(` ${unionName}: ${generateUnionVariantType(group, allNodes)};`);
|
|
126
|
+
}
|
|
127
|
+
lines.push("}");
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(`export class ${structName}Reader {`);
|
|
130
|
+
lines.push(" constructor(private reader: StructReader) {}");
|
|
131
|
+
lines.push("");
|
|
132
|
+
for (const field of regularFields) {
|
|
133
|
+
if (!field.isSlot) continue;
|
|
134
|
+
const getter = generateFieldGetter(field);
|
|
135
|
+
lines.push(` ${getter}`);
|
|
136
|
+
lines.push("");
|
|
137
|
+
}
|
|
138
|
+
for (const { field: groupField, groupNode } of groupFields) {
|
|
139
|
+
const groupName = groupField.name;
|
|
140
|
+
try {
|
|
141
|
+
for (const field of groupNode.structFields) {
|
|
142
|
+
if (!field.isSlot) continue;
|
|
143
|
+
const getter = generateGroupFieldGetter(field, groupName);
|
|
144
|
+
lines.push(` ${getter}`);
|
|
145
|
+
lines.push("");
|
|
146
|
+
}
|
|
147
|
+
} catch (_e) {}
|
|
148
|
+
}
|
|
149
|
+
for (const [groupIndex, group] of unionGroups.entries()) {
|
|
150
|
+
const unionName = group.fields.length > 0 ? `${capitalize(group.fields[0].name)}Union` : `Union${groupIndex}`;
|
|
151
|
+
const variants = {};
|
|
152
|
+
for (const field of group.fields) variants[field.discriminantValue] = field.name;
|
|
153
|
+
const variantsStr = JSON.stringify(variants).replace(/"/g, "'");
|
|
154
|
+
lines.push(` get${unionName}Tag(): number {`);
|
|
155
|
+
lines.push(` return this.reader.getUint16(${group.discriminantOffset * 2});`);
|
|
156
|
+
lines.push(" }");
|
|
157
|
+
lines.push("");
|
|
158
|
+
lines.push(` get${unionName}Variant(): string | undefined {`);
|
|
159
|
+
lines.push(` const tag = this.get${unionName}Tag();`);
|
|
160
|
+
lines.push(` const variants = ${variantsStr};`);
|
|
161
|
+
lines.push(" return variants[tag];");
|
|
162
|
+
lines.push(" }");
|
|
163
|
+
lines.push("");
|
|
164
|
+
for (const field of group.fields) {
|
|
165
|
+
const getter = generateUnionFieldGetter(field, unionName, field.discriminantValue);
|
|
166
|
+
lines.push(` ${getter}`);
|
|
167
|
+
lines.push("");
|
|
168
|
+
}
|
|
169
|
+
for (const field of group.fields) {
|
|
170
|
+
const setter = generateUnionFieldSetter(field, unionName, field.discriminantValue, group.discriminantOffset * 2);
|
|
171
|
+
lines.push(` ${setter}`);
|
|
172
|
+
lines.push("");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
lines.push("}");
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push(`export class ${structName}Builder {`);
|
|
178
|
+
lines.push(" constructor(private builder: StructBuilder) {}");
|
|
179
|
+
lines.push("");
|
|
180
|
+
for (const field of regularFields) {
|
|
181
|
+
if (!field.isSlot) continue;
|
|
182
|
+
const setter = generateFieldSetter(field);
|
|
183
|
+
lines.push(` ${setter}`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
for (const { field: groupField, groupNode } of groupFields) {
|
|
187
|
+
const groupName = groupField.name;
|
|
188
|
+
try {
|
|
189
|
+
for (const field of groupNode.structFields) {
|
|
190
|
+
if (!field.isSlot) continue;
|
|
191
|
+
const setter = generateGroupFieldSetter(field, groupName);
|
|
192
|
+
lines.push(` ${setter}`);
|
|
193
|
+
lines.push("");
|
|
194
|
+
}
|
|
195
|
+
} catch (_e) {}
|
|
196
|
+
}
|
|
197
|
+
for (const [groupIndex, group] of unionGroups.entries()) {
|
|
198
|
+
const unionName = group.fields.length > 0 ? `${capitalize(group.fields[0].name)}Union` : `Union${groupIndex}`;
|
|
199
|
+
for (const field of group.fields) {
|
|
200
|
+
const setter = generateUnionFieldSetter(field, unionName, field.discriminantValue, group.discriminantOffset * 2);
|
|
201
|
+
lines.push(` ${setter}`);
|
|
202
|
+
lines.push("");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
lines.push("}");
|
|
206
|
+
return lines.join("\n");
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 分析字段,分离 Union 字段和普通字段,展开 Group 字段
|
|
210
|
+
*/
|
|
211
|
+
function analyzeFields(node, allNodes) {
|
|
212
|
+
const fields = node.structFields;
|
|
213
|
+
const unionGroups = /* @__PURE__ */ new Map();
|
|
214
|
+
const regularFields = [];
|
|
215
|
+
const groupFields = [];
|
|
216
|
+
for (const field of fields) {
|
|
217
|
+
if (field.isGroup) {
|
|
218
|
+
const groupNode = allNodes.find((n) => n.id === field.groupTypeId);
|
|
219
|
+
if (groupNode?.isStruct) groupFields.push({
|
|
220
|
+
field,
|
|
221
|
+
groupNode
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (node.structDiscriminantCount > 0 && field.discriminantValue !== 65535 && field.discriminantValue !== 0) {
|
|
226
|
+
const discriminantOffset = node.structDiscriminantOffset;
|
|
227
|
+
if (!unionGroups.has(discriminantOffset)) unionGroups.set(discriminantOffset, {
|
|
228
|
+
discriminantOffset,
|
|
229
|
+
fields: []
|
|
230
|
+
});
|
|
231
|
+
unionGroups.get(discriminantOffset).fields.push(field);
|
|
232
|
+
} else regularFields.push(field);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
unionGroups: Array.from(unionGroups.values()),
|
|
236
|
+
regularFields,
|
|
237
|
+
groupFields
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 生成 Union variant 类型
|
|
242
|
+
*/
|
|
243
|
+
function generateUnionVariantType(group, allNodes) {
|
|
244
|
+
return group.fields.map((f) => {
|
|
245
|
+
const tsType = f.isSlot ? getTypeScriptType(f.slotType, allNodes) : "unknown";
|
|
246
|
+
return `{ tag: ${f.discriminantValue}; variant: '${f.name}'; value: ${tsType} }`;
|
|
247
|
+
}).join(" | ");
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 生成 Union 字段的 getter
|
|
251
|
+
*/
|
|
252
|
+
function generateUnionFieldGetter(field, unionName, discriminantValue) {
|
|
253
|
+
const name = field.name;
|
|
254
|
+
const type = field.slotType;
|
|
255
|
+
if (!type || !field.isSlot) return `get${capitalize(name)}(): unknown | undefined { return this.get${unionName}Tag() === ${discriminantValue} ? undefined : undefined; }`;
|
|
256
|
+
const returnType = getTypeScriptTypeForSetter(type);
|
|
257
|
+
let body;
|
|
258
|
+
switch (type.kind) {
|
|
259
|
+
case "void":
|
|
260
|
+
body = "return undefined;";
|
|
261
|
+
break;
|
|
262
|
+
case "bool":
|
|
263
|
+
body = `return this.reader.getBool(${field.slotOffset * 8});`;
|
|
264
|
+
break;
|
|
265
|
+
case "int8":
|
|
266
|
+
body = `return this.reader.getInt8(${field.slotOffset});`;
|
|
267
|
+
break;
|
|
268
|
+
case "int16":
|
|
269
|
+
body = `return this.reader.getInt16(${field.slotOffset * 2});`;
|
|
270
|
+
break;
|
|
271
|
+
case "int32":
|
|
272
|
+
body = `return this.reader.getInt32(${field.slotOffset * 4});`;
|
|
273
|
+
break;
|
|
274
|
+
case "int64":
|
|
275
|
+
body = `return this.reader.getInt64(${field.slotOffset * 8});`;
|
|
276
|
+
break;
|
|
277
|
+
case "uint8":
|
|
278
|
+
body = `return this.reader.getUint8(${field.slotOffset});`;
|
|
279
|
+
break;
|
|
280
|
+
case "uint16":
|
|
281
|
+
body = `return this.reader.getUint16(${field.slotOffset * 2});`;
|
|
282
|
+
break;
|
|
283
|
+
case "uint32":
|
|
284
|
+
body = `return this.reader.getUint32(${field.slotOffset * 4});`;
|
|
285
|
+
break;
|
|
286
|
+
case "uint64":
|
|
287
|
+
body = `return this.reader.getUint64(${field.slotOffset * 8});`;
|
|
288
|
+
break;
|
|
289
|
+
case "float32":
|
|
290
|
+
body = `return this.reader.getFloat32(${field.slotOffset * 4});`;
|
|
291
|
+
break;
|
|
292
|
+
case "float64":
|
|
293
|
+
body = `return this.reader.getFloat64(${field.slotOffset * 8});`;
|
|
294
|
+
break;
|
|
295
|
+
case "text":
|
|
296
|
+
body = `return this.reader.getText(${field.slotOffset});`;
|
|
297
|
+
break;
|
|
298
|
+
case "data":
|
|
299
|
+
body = `return this.reader.getData(${field.slotOffset});`;
|
|
300
|
+
break;
|
|
301
|
+
default: body = "return undefined;";
|
|
302
|
+
}
|
|
303
|
+
return `get${capitalize(name)}(): ${returnType} | undefined {
|
|
304
|
+
if (this.get${unionName}Tag() !== ${discriminantValue}) return undefined;
|
|
305
|
+
${body}
|
|
306
|
+
}`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 生成 Group 字段的 getter
|
|
310
|
+
* Group 字段在父 struct 的 data/pointer section 中,但命名空间是 Group 的名字
|
|
311
|
+
*/
|
|
312
|
+
function generateGroupFieldGetter(field, groupName) {
|
|
313
|
+
const name = field.name;
|
|
314
|
+
const type = field.slotType;
|
|
315
|
+
if (!type) return `get${capitalize(groupName)}${capitalize(name)}(): unknown { return undefined; }`;
|
|
316
|
+
switch (type.kind) {
|
|
317
|
+
case "void": return `get${capitalize(groupName)}${capitalize(name)}(): void { return undefined; }`;
|
|
318
|
+
case "bool": return `get${capitalize(groupName)}${capitalize(name)}(): boolean { return this.reader.getBool(${field.slotOffset * 8}); }`;
|
|
319
|
+
case "int8": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getInt8(${field.slotOffset}); }`;
|
|
320
|
+
case "int16": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getInt16(${field.slotOffset * 2}); }`;
|
|
321
|
+
case "int32": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getInt32(${field.slotOffset * 4}); }`;
|
|
322
|
+
case "int64": return `get${capitalize(groupName)}${capitalize(name)}(): bigint { return this.reader.getInt64(${field.slotOffset * 8}); }`;
|
|
323
|
+
case "uint8": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getUint8(${field.slotOffset}); }`;
|
|
324
|
+
case "uint16": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getUint16(${field.slotOffset * 2}); }`;
|
|
325
|
+
case "uint32": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getUint32(${field.slotOffset * 4}); }`;
|
|
326
|
+
case "uint64": return `get${capitalize(groupName)}${capitalize(name)}(): bigint { return this.reader.getUint64(${field.slotOffset * 8}); }`;
|
|
327
|
+
case "float32": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getFloat32(${field.slotOffset * 4}); }`;
|
|
328
|
+
case "float64": return `get${capitalize(groupName)}${capitalize(name)}(): number { return this.reader.getFloat64(${field.slotOffset * 8}); }`;
|
|
329
|
+
case "text": return `get${capitalize(groupName)}${capitalize(name)}(): string { return this.reader.getText(${field.slotOffset}); }`;
|
|
330
|
+
case "data": return `get${capitalize(groupName)}${capitalize(name)}(): Uint8Array { return this.reader.getData(${field.slotOffset}); }`;
|
|
331
|
+
default: return `get${capitalize(groupName)}${capitalize(name)}(): unknown { return undefined; }`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* 生成 Group 字段的 setter
|
|
336
|
+
*/
|
|
337
|
+
function generateGroupFieldSetter(field, groupName) {
|
|
338
|
+
const name = field.name;
|
|
339
|
+
const type = field.slotType;
|
|
340
|
+
const paramType = getTypeScriptTypeForSetter(type);
|
|
341
|
+
if (!type || !field.isSlot) return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { /* TODO */ }`;
|
|
342
|
+
switch (type.kind) {
|
|
343
|
+
case "void": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { }`;
|
|
344
|
+
case "bool": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setBool(${field.slotOffset * 8}, value); }`;
|
|
345
|
+
case "int8": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setInt8(${field.slotOffset}, value); }`;
|
|
346
|
+
case "int16": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setInt16(${field.slotOffset * 2}, value); }`;
|
|
347
|
+
case "int32": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setInt32(${field.slotOffset * 4}, value); }`;
|
|
348
|
+
case "int64": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setInt64(${field.slotOffset * 8}, value); }`;
|
|
349
|
+
case "uint8": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setUint8(${field.slotOffset}, value); }`;
|
|
350
|
+
case "uint16": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setUint16(${field.slotOffset * 2}, value); }`;
|
|
351
|
+
case "uint32": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setUint32(${field.slotOffset * 4}, value); }`;
|
|
352
|
+
case "uint64": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setUint64(${field.slotOffset * 8}, value); }`;
|
|
353
|
+
case "float32": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setFloat32(${field.slotOffset * 4}, value); }`;
|
|
354
|
+
case "float64": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setFloat64(${field.slotOffset * 8}, value); }`;
|
|
355
|
+
case "text": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setText(${field.slotOffset}, value); }`;
|
|
356
|
+
case "data": return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { this.builder.setData(${field.slotOffset}, value); }`;
|
|
357
|
+
default: return `set${capitalize(groupName)}${capitalize(name)}(value: ${paramType}): void { /* TODO */ }`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 生成 Union 字段的 setter
|
|
362
|
+
*/
|
|
363
|
+
function generateUnionFieldSetter(field, _unionName, discriminantValue, discriminantOffset) {
|
|
364
|
+
const name = field.name;
|
|
365
|
+
const type = field.slotType;
|
|
366
|
+
const paramType = getTypeScriptTypeForSetter(type);
|
|
367
|
+
if (!type || !field.isSlot) return `set${capitalize(name)}(value: ${paramType}): void {
|
|
368
|
+
this.builder.setUint16(${discriminantOffset}, ${discriminantValue});
|
|
369
|
+
}`;
|
|
370
|
+
let body;
|
|
371
|
+
switch (type.kind) {
|
|
372
|
+
case "void":
|
|
373
|
+
body = "";
|
|
374
|
+
break;
|
|
375
|
+
case "bool":
|
|
376
|
+
body = `this.builder.setBool(${field.slotOffset * 8}, value);`;
|
|
377
|
+
break;
|
|
378
|
+
case "int8":
|
|
379
|
+
body = `this.builder.setInt8(${field.slotOffset}, value);`;
|
|
380
|
+
break;
|
|
381
|
+
case "int16":
|
|
382
|
+
body = `this.builder.setInt16(${field.slotOffset * 2}, value);`;
|
|
383
|
+
break;
|
|
384
|
+
case "int32":
|
|
385
|
+
body = `this.builder.setInt32(${field.slotOffset * 4}, value);`;
|
|
386
|
+
break;
|
|
387
|
+
case "int64":
|
|
388
|
+
body = `this.builder.setInt64(${field.slotOffset * 8}, value);`;
|
|
389
|
+
break;
|
|
390
|
+
case "uint8":
|
|
391
|
+
body = `this.builder.setUint8(${field.slotOffset}, value);`;
|
|
392
|
+
break;
|
|
393
|
+
case "uint16":
|
|
394
|
+
body = `this.builder.setUint16(${field.slotOffset * 2}, value);`;
|
|
395
|
+
break;
|
|
396
|
+
case "uint32":
|
|
397
|
+
body = `this.builder.setUint32(${field.slotOffset * 4}, value);`;
|
|
398
|
+
break;
|
|
399
|
+
case "uint64":
|
|
400
|
+
body = `this.builder.setUint64(${field.slotOffset * 8}, value);`;
|
|
401
|
+
break;
|
|
402
|
+
case "float32":
|
|
403
|
+
body = `this.builder.setFloat32(${field.slotOffset * 4}, value);`;
|
|
404
|
+
break;
|
|
405
|
+
case "float64":
|
|
406
|
+
body = `this.builder.setFloat64(${field.slotOffset * 8}, value);`;
|
|
407
|
+
break;
|
|
408
|
+
case "text":
|
|
409
|
+
body = `this.builder.setText(${field.slotOffset}, value);`;
|
|
410
|
+
break;
|
|
411
|
+
case "data":
|
|
412
|
+
body = `this.builder.setData(${field.slotOffset}, value);`;
|
|
413
|
+
break;
|
|
414
|
+
default: body = "";
|
|
415
|
+
}
|
|
416
|
+
return `set${capitalize(name)}(value: ${paramType}): void {
|
|
417
|
+
this.builder.setUint16(${discriminantOffset}, ${discriminantValue});
|
|
418
|
+
${body}
|
|
419
|
+
}`;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 从 ValueReader 提取默认值,用于代码生成
|
|
423
|
+
*/
|
|
424
|
+
function extractDefaultValue(field) {
|
|
425
|
+
const defaultValue = field.defaultValue;
|
|
426
|
+
if (!defaultValue) return void 0;
|
|
427
|
+
const type = field.slotType;
|
|
428
|
+
if (!type) return void 0;
|
|
429
|
+
switch (type.kind) {
|
|
430
|
+
case "bool":
|
|
431
|
+
if (defaultValue.isBool) return defaultValue.boolValue ? "true" : "false";
|
|
432
|
+
return;
|
|
433
|
+
case "int8":
|
|
434
|
+
if (defaultValue.isInt8) return `${defaultValue.int8Value}`;
|
|
435
|
+
return;
|
|
436
|
+
case "int16":
|
|
437
|
+
if (defaultValue.isInt16) return `${defaultValue.int16Value}`;
|
|
438
|
+
return;
|
|
439
|
+
case "int32":
|
|
440
|
+
if (defaultValue.isInt32) return `${defaultValue.int32Value}`;
|
|
441
|
+
return;
|
|
442
|
+
case "int64":
|
|
443
|
+
if (defaultValue.isInt64) return `${defaultValue.int64Value}n`;
|
|
444
|
+
return;
|
|
445
|
+
case "uint8":
|
|
446
|
+
if (defaultValue.isUint8) return `${defaultValue.uint8Value}`;
|
|
447
|
+
return;
|
|
448
|
+
case "uint16":
|
|
449
|
+
if (defaultValue.isUint16) return `${defaultValue.uint16Value}`;
|
|
450
|
+
return;
|
|
451
|
+
case "uint32":
|
|
452
|
+
if (defaultValue.isUint32) return `${defaultValue.uint32Value}`;
|
|
453
|
+
return;
|
|
454
|
+
case "uint64":
|
|
455
|
+
if (defaultValue.isUint64) return `${defaultValue.uint64Value}n`;
|
|
456
|
+
return;
|
|
457
|
+
case "float32":
|
|
458
|
+
if (defaultValue.isFloat32) {
|
|
459
|
+
const val = defaultValue.float32Value;
|
|
460
|
+
if (Number.isNaN(val)) return "NaN";
|
|
461
|
+
if (val === Number.POSITIVE_INFINITY) return "Infinity";
|
|
462
|
+
if (val === Number.NEGATIVE_INFINITY) return "-Infinity";
|
|
463
|
+
return `${val}`;
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
case "float64":
|
|
467
|
+
if (defaultValue.isFloat64) {
|
|
468
|
+
const val = defaultValue.float64Value;
|
|
469
|
+
if (Number.isNaN(val)) return "NaN";
|
|
470
|
+
if (val === Number.POSITIVE_INFINITY) return "Infinity";
|
|
471
|
+
if (val === Number.NEGATIVE_INFINITY) return "-Infinity";
|
|
472
|
+
return `${val}`;
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
case "enum":
|
|
476
|
+
if (defaultValue.isEnum) return `${defaultValue.enumValue}`;
|
|
477
|
+
return;
|
|
478
|
+
default: return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 生成 XOR 解码表达式(用于 getter)
|
|
483
|
+
* stored ^ default = actual
|
|
484
|
+
*/
|
|
485
|
+
function generateXorDecode(expr, defaultValue, typeKind) {
|
|
486
|
+
switch (typeKind) {
|
|
487
|
+
case "bool": return `${expr} !== ${defaultValue}`;
|
|
488
|
+
case "int8":
|
|
489
|
+
case "int16":
|
|
490
|
+
case "int32": return `(${expr} ^ ${defaultValue}) | 0`;
|
|
491
|
+
case "uint8":
|
|
492
|
+
case "uint16":
|
|
493
|
+
case "uint32": return `${expr} ^ ${defaultValue}`;
|
|
494
|
+
case "int64":
|
|
495
|
+
case "uint64": return `${expr} ^ ${defaultValue}`;
|
|
496
|
+
case "float32": return `xorFloat32(${expr}, ${defaultValue})`;
|
|
497
|
+
case "float64": return `xorFloat64(${expr}, ${defaultValue})`;
|
|
498
|
+
case "enum": return `${expr} ^ ${defaultValue}`;
|
|
499
|
+
default: return expr;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* 生成 XOR 编码表达式(用于 setter)
|
|
504
|
+
* actual ^ default = stored
|
|
505
|
+
*/
|
|
506
|
+
function generateXorEncode(valueExpr, defaultValue, typeKind) {
|
|
507
|
+
switch (typeKind) {
|
|
508
|
+
case "bool": return `${valueExpr} !== ${defaultValue}`;
|
|
509
|
+
case "int8":
|
|
510
|
+
case "int16":
|
|
511
|
+
case "int32": return `(${valueExpr} >>> 0) ^ ${defaultValue}`;
|
|
512
|
+
case "uint8":
|
|
513
|
+
case "uint16":
|
|
514
|
+
case "uint32": return `${valueExpr} ^ ${defaultValue}`;
|
|
515
|
+
case "int64":
|
|
516
|
+
case "uint64": return `${valueExpr} ^ ${defaultValue}`;
|
|
517
|
+
case "float32": return `xorFloat32(${valueExpr}, ${defaultValue})`;
|
|
518
|
+
case "float64": return `xorFloat64(${valueExpr}, ${defaultValue})`;
|
|
519
|
+
case "enum": return `${valueExpr} ^ ${defaultValue}`;
|
|
520
|
+
default: return valueExpr;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* 生成字段 getter
|
|
525
|
+
*/
|
|
526
|
+
function generateFieldGetter(field) {
|
|
527
|
+
const name = field.name;
|
|
528
|
+
const type = field.slotType;
|
|
529
|
+
const defaultValue = extractDefaultValue(field);
|
|
530
|
+
if (!type) return `get ${name}(): unknown { return undefined; }`;
|
|
531
|
+
if (defaultValue !== void 0 && type.kind !== "text" && type.kind !== "data") {
|
|
532
|
+
const decodedExpr = generateXorDecode(`this.reader.get${capitalize(type.kind)}(${getByteOffset(field, type.kind)})`, defaultValue, type.kind);
|
|
533
|
+
return `get ${name}(): ${getTypeScriptTypeForSetter(type)} { return ${decodedExpr}; }`;
|
|
534
|
+
}
|
|
535
|
+
switch (type.kind) {
|
|
536
|
+
case "void": return `get ${name}(): void { return undefined; }`;
|
|
537
|
+
case "bool": return `get ${name}(): boolean { return this.reader.getBool(${field.slotOffset * 8}); }`;
|
|
538
|
+
case "int8": return `get ${name}(): number { return this.reader.getInt8(${field.slotOffset}); }`;
|
|
539
|
+
case "int16": return `get ${name}(): number { return this.reader.getInt16(${field.slotOffset * 2}); }`;
|
|
540
|
+
case "int32": return `get ${name}(): number { return this.reader.getInt32(${field.slotOffset * 4}); }`;
|
|
541
|
+
case "int64": return `get ${name}(): bigint { return this.reader.getInt64(${field.slotOffset * 8}); }`;
|
|
542
|
+
case "uint8": return `get ${name}(): number { return this.reader.getUint8(${field.slotOffset}); }`;
|
|
543
|
+
case "uint16": return `get ${name}(): number { return this.reader.getUint16(${field.slotOffset * 2}); }`;
|
|
544
|
+
case "uint32": return `get ${name}(): number { return this.reader.getUint32(${field.slotOffset * 4}); }`;
|
|
545
|
+
case "uint64": return `get ${name}(): bigint { return this.reader.getUint64(${field.slotOffset * 8}); }`;
|
|
546
|
+
case "float32": return `get ${name}(): number { return this.reader.getFloat32(${field.slotOffset * 4}); }`;
|
|
547
|
+
case "float64": return `get ${name}(): number { return this.reader.getFloat64(${field.slotOffset * 8}); }`;
|
|
548
|
+
case "text": return `get ${name}(): string { return this.reader.getText(${field.slotOffset}); }`;
|
|
549
|
+
case "data": return `get ${name}(): Uint8Array { return this.reader.getData(${field.slotOffset}); }`;
|
|
550
|
+
default: return `get ${name}(): unknown { return undefined; }`;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 获取字节偏移量
|
|
555
|
+
*/
|
|
556
|
+
function getByteOffset(field, typeKind) {
|
|
557
|
+
switch (typeKind) {
|
|
558
|
+
case "bool": return field.slotOffset * 8;
|
|
559
|
+
case "int8":
|
|
560
|
+
case "uint8": return field.slotOffset;
|
|
561
|
+
case "int16":
|
|
562
|
+
case "uint16":
|
|
563
|
+
case "enum": return field.slotOffset * 2;
|
|
564
|
+
case "int32":
|
|
565
|
+
case "uint32":
|
|
566
|
+
case "float32": return field.slotOffset * 4;
|
|
567
|
+
case "int64":
|
|
568
|
+
case "uint64":
|
|
569
|
+
case "float64": return field.slotOffset * 8;
|
|
570
|
+
default: return field.slotOffset;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 生成字段 setter
|
|
575
|
+
*/
|
|
576
|
+
function generateFieldSetter(field) {
|
|
577
|
+
const name = field.name;
|
|
578
|
+
const type = field.slotType;
|
|
579
|
+
const paramType = getTypeScriptTypeForSetter(type);
|
|
580
|
+
const defaultValue = extractDefaultValue(field);
|
|
581
|
+
if (!type) return `set${capitalize(name)}(value: unknown): void { /* TODO */ }`;
|
|
582
|
+
if (defaultValue !== void 0 && type.kind !== "text" && type.kind !== "data") {
|
|
583
|
+
const encodedExpr = generateXorEncode("value", defaultValue, type.kind);
|
|
584
|
+
const setterMethod = `set${capitalize(type.kind)}`;
|
|
585
|
+
return `set${capitalize(name)}(value: ${paramType}): void { this.builder.${setterMethod}(${getByteOffset(field, type.kind)}, ${encodedExpr}); }`;
|
|
586
|
+
}
|
|
587
|
+
switch (type.kind) {
|
|
588
|
+
case "void": return `set${capitalize(name)}(value: void): void { /* void */ }`;
|
|
589
|
+
case "bool": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setBool(${field.slotOffset * 8}, value); }`;
|
|
590
|
+
case "int8": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setInt8(${field.slotOffset}, value); }`;
|
|
591
|
+
case "int16": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setInt16(${field.slotOffset * 2}, value); }`;
|
|
592
|
+
case "int32": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setInt32(${field.slotOffset * 4}, value); }`;
|
|
593
|
+
case "int64": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setInt64(${field.slotOffset * 8}, value); }`;
|
|
594
|
+
case "uint8": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setUint8(${field.slotOffset}, value); }`;
|
|
595
|
+
case "uint16": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setUint16(${field.slotOffset * 2}, value); }`;
|
|
596
|
+
case "uint32": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setUint32(${field.slotOffset * 4}, value); }`;
|
|
597
|
+
case "uint64": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setUint64(${field.slotOffset * 8}, value); }`;
|
|
598
|
+
case "float32": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setFloat32(${field.slotOffset * 4}, value); }`;
|
|
599
|
+
case "float64": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setFloat64(${field.slotOffset * 8}, value); }`;
|
|
600
|
+
case "text": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setText(${field.slotOffset}, value); }`;
|
|
601
|
+
case "data": return `set${capitalize(name)}(value: ${paramType}): void { this.builder.setData(${field.slotOffset}, value); }`;
|
|
602
|
+
default: return `set${capitalize(name)}(value: ${paramType}): void { /* TODO */ }`;
|
|
62
603
|
}
|
|
63
604
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
switch (type) {
|
|
70
|
-
case "
|
|
71
|
-
case "
|
|
72
|
-
case "
|
|
73
|
-
case "
|
|
74
|
-
case "
|
|
75
|
-
case "
|
|
76
|
-
case "
|
|
77
|
-
case "
|
|
78
|
-
case "
|
|
79
|
-
case "
|
|
80
|
-
case "
|
|
81
|
-
case "
|
|
82
|
-
case "
|
|
83
|
-
case "
|
|
84
|
-
default: return "
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function getSetterMethod(type) {
|
|
88
|
-
if (typeof type !== "string") {
|
|
89
|
-
if (type.kind === "list") return "initList";
|
|
90
|
-
return "initStruct";
|
|
91
|
-
}
|
|
92
|
-
switch (type) {
|
|
93
|
-
case "Void": return "setVoid";
|
|
94
|
-
case "Bool": return "setBool";
|
|
95
|
-
case "Int8": return "setInt8";
|
|
96
|
-
case "Int16": return "setInt16";
|
|
97
|
-
case "Int32": return "setInt32";
|
|
98
|
-
case "Int64": return "setInt64";
|
|
99
|
-
case "UInt8": return "setUint8";
|
|
100
|
-
case "UInt16": return "setUint16";
|
|
101
|
-
case "UInt32": return "setUint32";
|
|
102
|
-
case "UInt64": return "setUint64";
|
|
103
|
-
case "Float32": return "setFloat32";
|
|
104
|
-
case "Float64": return "setFloat64";
|
|
105
|
-
case "Text": return "setText";
|
|
106
|
-
case "Data": return "setData";
|
|
107
|
-
default: return "setUnknown";
|
|
605
|
+
/**
|
|
606
|
+
* 获取 TypeScript setter 参数类型
|
|
607
|
+
*/
|
|
608
|
+
function getTypeScriptTypeForSetter(type) {
|
|
609
|
+
if (!type) return "unknown";
|
|
610
|
+
switch (type.kind) {
|
|
611
|
+
case "void": return "void";
|
|
612
|
+
case "bool": return "boolean";
|
|
613
|
+
case "int8":
|
|
614
|
+
case "int16":
|
|
615
|
+
case "int32":
|
|
616
|
+
case "uint8":
|
|
617
|
+
case "uint16":
|
|
618
|
+
case "uint32":
|
|
619
|
+
case "float32":
|
|
620
|
+
case "float64": return "number";
|
|
621
|
+
case "int64":
|
|
622
|
+
case "uint64": return "bigint";
|
|
623
|
+
case "text": return "string";
|
|
624
|
+
case "data": return "Uint8Array";
|
|
625
|
+
default: return "unknown";
|
|
108
626
|
}
|
|
109
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* 首字母大写
|
|
630
|
+
*/
|
|
110
631
|
function capitalize(str) {
|
|
111
632
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
112
633
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for (const
|
|
121
|
-
else dataOffset += getTypeSize(field.type);
|
|
122
|
-
dataWords = Math.ceil(dataOffset / 8);
|
|
123
|
-
lines.push(`export interface ${struct.name} {`);
|
|
124
|
-
for (const field of struct.fields) lines.push(` ${field.name}: ${mapTypeToTs(field.type)};`);
|
|
634
|
+
/**
|
|
635
|
+
* 生成 Enum 的 TypeScript 代码
|
|
636
|
+
*/
|
|
637
|
+
function generateEnum(node) {
|
|
638
|
+
const lines = [];
|
|
639
|
+
const enumName = getShortName(node.displayName);
|
|
640
|
+
lines.push(`export enum ${enumName} {`);
|
|
641
|
+
for (const enumerant of node.enumEnumerants) lines.push(` ${enumerant.name} = ${enumerant.codeOrder},`);
|
|
125
642
|
lines.push("}");
|
|
126
|
-
lines.
|
|
127
|
-
generateReader(struct, dataWords, pointerCount, lines);
|
|
128
|
-
generateBuilder(struct, dataWords, pointerCount, lines);
|
|
643
|
+
return lines.join("\n");
|
|
129
644
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
645
|
+
/**
|
|
646
|
+
* 获取 TypeScript 类型字符串
|
|
647
|
+
*/
|
|
648
|
+
function getTypeScriptType(type, allNodes) {
|
|
649
|
+
if (!type) return "unknown";
|
|
650
|
+
switch (type.kind) {
|
|
651
|
+
case "void": return "void";
|
|
652
|
+
case "bool": return "boolean";
|
|
653
|
+
case "int8":
|
|
654
|
+
case "int16":
|
|
655
|
+
case "int32":
|
|
656
|
+
case "uint8":
|
|
657
|
+
case "uint16":
|
|
658
|
+
case "uint32":
|
|
659
|
+
case "float32":
|
|
660
|
+
case "float64": return "number";
|
|
661
|
+
case "int64":
|
|
662
|
+
case "uint64": return "bigint";
|
|
663
|
+
case "text": return "string";
|
|
664
|
+
case "data": return "Uint8Array";
|
|
665
|
+
case "list": {
|
|
666
|
+
const elementType = type.listElementType;
|
|
667
|
+
if (!elementType) return "unknown[]";
|
|
668
|
+
return `${getTypeScriptType(elementType, allNodes)}[]`;
|
|
152
669
|
}
|
|
153
|
-
|
|
670
|
+
case "struct": {
|
|
671
|
+
const structNode = allNodes.find((n) => n.id === type.typeId);
|
|
672
|
+
if (structNode) return getShortName(structNode.displayName);
|
|
673
|
+
return "unknown";
|
|
674
|
+
}
|
|
675
|
+
case "enum": {
|
|
676
|
+
const enumNode = allNodes.find((n) => n.id === type.typeId);
|
|
677
|
+
if (enumNode) return getShortName(enumNode.displayName);
|
|
678
|
+
return "unknown";
|
|
679
|
+
}
|
|
680
|
+
default: return "unknown";
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* 从 displayName 获取短名称
|
|
685
|
+
* 例如 "test-schema.capnp:Person" -> "Person"
|
|
686
|
+
* 处理 auto-generated 名称如 "Calculator.evaluate$Params" -> "EvaluateParams"
|
|
687
|
+
*/
|
|
688
|
+
function getShortName(displayName) {
|
|
689
|
+
if (!displayName) return "Unknown";
|
|
690
|
+
const colonIndex = displayName.lastIndexOf(":");
|
|
691
|
+
let name = colonIndex >= 0 ? displayName.substring(colonIndex + 1) : displayName;
|
|
692
|
+
if (name.includes(".")) {
|
|
693
|
+
const parts = name.split(".");
|
|
694
|
+
if (parts.length === 2 && parts[1].includes("$")) {
|
|
695
|
+
const methodPart = parts[1];
|
|
696
|
+
const dollarIndex = methodPart.indexOf("$");
|
|
697
|
+
if (dollarIndex > 0) {
|
|
698
|
+
const methodName = methodPart.substring(0, dollarIndex);
|
|
699
|
+
const suffix = methodPart.substring(dollarIndex + 1);
|
|
700
|
+
name = capitalize(methodName) + suffix;
|
|
701
|
+
} else name = capitalize(parts[1]);
|
|
702
|
+
} else name = parts[parts.length - 1];
|
|
703
|
+
}
|
|
704
|
+
name = name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
705
|
+
return name;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* 生成 Interface 的 TypeScript 代码
|
|
709
|
+
* 包括:Method Constants、Client Class、Server Interface、Server Stub
|
|
710
|
+
*/
|
|
711
|
+
function generateInterface(node, allNodes) {
|
|
712
|
+
const lines = [];
|
|
713
|
+
const interfaceName = getShortName(node.displayName);
|
|
714
|
+
const methods = node.interfaceMethods;
|
|
715
|
+
if (methods.length === 0) return `// Interface ${interfaceName} has no methods`;
|
|
716
|
+
lines.push(`// ${interfaceName} Method IDs`);
|
|
717
|
+
lines.push(`export const ${interfaceName}InterfaceId = ${node.id}n;`);
|
|
718
|
+
lines.push(`export const ${interfaceName}MethodIds = {`);
|
|
719
|
+
for (const method of methods) lines.push(` ${method.name}: ${method.codeOrder},`);
|
|
720
|
+
lines.push("} as const;");
|
|
721
|
+
lines.push("");
|
|
722
|
+
lines.push(`// ${interfaceName} Server Interface`);
|
|
723
|
+
lines.push(`export interface ${interfaceName}Server {`);
|
|
724
|
+
for (const method of methods) {
|
|
725
|
+
const paramType = getTypeNameById(method.paramStructType, allNodes, "unknown");
|
|
726
|
+
const resultType = getTypeNameById(method.resultStructType, allNodes, "unknown");
|
|
727
|
+
lines.push(` ${method.name}(context: CallContext<${paramType}Reader, ${resultType}Builder>): Promise<void> | void;`);
|
|
154
728
|
}
|
|
155
729
|
lines.push("}");
|
|
156
730
|
lines.push("");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
lines.push(`
|
|
160
|
-
lines.push(" private builder: StructBuilder;");
|
|
731
|
+
lines.push(`// ${interfaceName} Server Stub`);
|
|
732
|
+
lines.push(`export class ${interfaceName}Stub {`);
|
|
733
|
+
lines.push(` private server: ${interfaceName}Server;`);
|
|
161
734
|
lines.push("");
|
|
162
|
-
lines.push(
|
|
163
|
-
lines.push(" this.
|
|
735
|
+
lines.push(` constructor(server: ${interfaceName}Server) {`);
|
|
736
|
+
lines.push(" this.server = server;");
|
|
164
737
|
lines.push(" }");
|
|
165
738
|
lines.push("");
|
|
166
|
-
lines.push(` static
|
|
167
|
-
lines.push(
|
|
168
|
-
lines.push(
|
|
739
|
+
lines.push(` static readonly interfaceId = ${node.id}n;`);
|
|
740
|
+
lines.push("");
|
|
741
|
+
lines.push(" /** Dispatch a method call to the appropriate handler */");
|
|
742
|
+
lines.push(" async dispatch(methodId: number, context: CallContext<unknown, unknown>): Promise<void> {");
|
|
743
|
+
lines.push(" switch (methodId) {");
|
|
744
|
+
for (const method of methods) {
|
|
745
|
+
const paramType = getTypeNameById(method.paramStructType, allNodes, "unknown");
|
|
746
|
+
const resultType = getTypeNameById(method.resultStructType, allNodes, "unknown");
|
|
747
|
+
lines.push(` case ${interfaceName}MethodIds.${method.name}:`);
|
|
748
|
+
lines.push(` return this.server.${method.name}(context as CallContext<${paramType}Reader, ${resultType}Builder>);`);
|
|
749
|
+
}
|
|
750
|
+
lines.push(" default:");
|
|
751
|
+
lines.push(" throw new Error(`Unknown method ID: ${methodId}`);");
|
|
752
|
+
lines.push(" }");
|
|
169
753
|
lines.push(" }");
|
|
170
754
|
lines.push("");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
755
|
+
lines.push(" /** Check if a method ID is valid */");
|
|
756
|
+
lines.push(" isValidMethod(methodId: number): boolean {");
|
|
757
|
+
lines.push(" return [");
|
|
758
|
+
for (const method of methods) lines.push(` ${interfaceName}MethodIds.${method.name},`);
|
|
759
|
+
lines.push(" ].includes(methodId);");
|
|
760
|
+
lines.push(" }");
|
|
761
|
+
lines.push("}");
|
|
762
|
+
lines.push("");
|
|
763
|
+
lines.push(`// ${interfaceName} Client Class`);
|
|
764
|
+
lines.push(`export class ${interfaceName}Client extends BaseCapabilityClient {`);
|
|
765
|
+
lines.push(` static readonly interfaceId = ${node.id}n;`);
|
|
766
|
+
lines.push("");
|
|
767
|
+
for (const method of methods) {
|
|
768
|
+
const paramType = getTypeNameById(method.paramStructType, allNodes, "unknown");
|
|
769
|
+
const resultType = getTypeNameById(method.resultStructType, allNodes, "unknown");
|
|
770
|
+
lines.push(" /**");
|
|
771
|
+
lines.push(` * ${method.name}`);
|
|
772
|
+
lines.push(` * @param params - ${paramType}`);
|
|
773
|
+
lines.push(` * @returns PipelineClient<${resultType}Reader>`);
|
|
774
|
+
lines.push(" */");
|
|
775
|
+
lines.push(` ${method.name}(params: ${paramType}Builder): PipelineClient<${resultType}Reader> {`);
|
|
776
|
+
lines.push(" return this._call(");
|
|
777
|
+
lines.push(` ${interfaceName}Client.interfaceId,`);
|
|
778
|
+
lines.push(` ${interfaceName}MethodIds.${method.name},`);
|
|
779
|
+
lines.push(" params");
|
|
780
|
+
lines.push(` ) as PipelineClient<${resultType}Reader>;`);
|
|
781
|
+
lines.push(" }");
|
|
187
782
|
lines.push("");
|
|
188
783
|
}
|
|
189
784
|
lines.push("}");
|
|
190
|
-
lines.
|
|
785
|
+
return lines.join("\n");
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* 根据类型 ID 获取类型名称
|
|
789
|
+
*/
|
|
790
|
+
function getTypeNameById(id, allNodes, fallback) {
|
|
791
|
+
const node = allNodes.find((n) => n.id === id);
|
|
792
|
+
if (!node) return fallback;
|
|
793
|
+
return getShortName(node.displayName);
|
|
191
794
|
}
|
|
192
795
|
|
|
193
796
|
//#endregion
|
|
194
|
-
//#region src/
|
|
797
|
+
//#region src/core/pointer.ts
|
|
195
798
|
/**
|
|
196
|
-
*
|
|
799
|
+
* Cap'n Proto 指针编解码
|
|
800
|
+
* 纯 TypeScript 实现
|
|
197
801
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
802
|
+
let PointerTag = /* @__PURE__ */ function(PointerTag) {
|
|
803
|
+
PointerTag[PointerTag["STRUCT"] = 0] = "STRUCT";
|
|
804
|
+
PointerTag[PointerTag["LIST"] = 1] = "LIST";
|
|
805
|
+
PointerTag[PointerTag["FAR"] = 2] = "FAR";
|
|
806
|
+
PointerTag[PointerTag["OTHER"] = 3] = "OTHER";
|
|
807
|
+
return PointerTag;
|
|
808
|
+
}({});
|
|
809
|
+
let ElementSize = /* @__PURE__ */ function(ElementSize) {
|
|
810
|
+
ElementSize[ElementSize["VOID"] = 0] = "VOID";
|
|
811
|
+
ElementSize[ElementSize["BIT"] = 1] = "BIT";
|
|
812
|
+
ElementSize[ElementSize["BYTE"] = 2] = "BYTE";
|
|
813
|
+
ElementSize[ElementSize["TWO_BYTES"] = 3] = "TWO_BYTES";
|
|
814
|
+
ElementSize[ElementSize["FOUR_BYTES"] = 4] = "FOUR_BYTES";
|
|
815
|
+
ElementSize[ElementSize["EIGHT_BYTES"] = 5] = "EIGHT_BYTES";
|
|
816
|
+
ElementSize[ElementSize["POINTER"] = 6] = "POINTER";
|
|
817
|
+
ElementSize[ElementSize["COMPOSITE"] = 7] = "COMPOSITE";
|
|
818
|
+
ElementSize[ElementSize["INLINE_COMPOSITE"] = 7] = "INLINE_COMPOSITE";
|
|
819
|
+
return ElementSize;
|
|
820
|
+
}({});
|
|
821
|
+
/**
|
|
822
|
+
* 解码指针(64位)
|
|
823
|
+
*/
|
|
824
|
+
function decodePointer(ptr) {
|
|
825
|
+
const tag = Number(ptr & BigInt(3));
|
|
826
|
+
switch (tag) {
|
|
827
|
+
case PointerTag.STRUCT: {
|
|
828
|
+
const offset = Number(ptr >> BigInt(2)) & 1073741823;
|
|
829
|
+
return {
|
|
830
|
+
tag,
|
|
831
|
+
offset: offset >= 536870912 ? offset - 1073741824 : offset,
|
|
832
|
+
dataWords: Number(ptr >> BigInt(32) & BigInt(65535)),
|
|
833
|
+
pointerCount: Number(ptr >> BigInt(48) & BigInt(65535))
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
case PointerTag.LIST: {
|
|
837
|
+
const offset = Number(ptr >> BigInt(2)) & 1073741823;
|
|
838
|
+
return {
|
|
839
|
+
tag,
|
|
840
|
+
offset: offset >= 536870912 ? offset - 1073741824 : offset,
|
|
841
|
+
elementSize: Number(ptr >> BigInt(32) & BigInt(7)),
|
|
842
|
+
elementCount: Number(ptr >> BigInt(35) & BigInt(536870911))
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
case PointerTag.FAR: {
|
|
846
|
+
const doubleFar = Boolean(ptr >> BigInt(2) & BigInt(1));
|
|
847
|
+
const targetOffset = Number(ptr >> BigInt(3) & BigInt(536870911));
|
|
848
|
+
return {
|
|
849
|
+
tag,
|
|
850
|
+
doubleFar,
|
|
851
|
+
targetSegment: Number(ptr >> BigInt(32) & BigInt(4294967295)),
|
|
852
|
+
targetOffset
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
default: return { tag: PointerTag.OTHER };
|
|
856
|
+
}
|
|
213
857
|
}
|
|
214
858
|
|
|
215
859
|
//#endregion
|
|
216
|
-
//#region src/
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
860
|
+
//#region src/core/segment.ts
|
|
861
|
+
/**
|
|
862
|
+
* Cap'n Proto Segment 管理
|
|
863
|
+
* 纯 TypeScript 实现
|
|
864
|
+
*/
|
|
865
|
+
const WORD_SIZE = 8;
|
|
866
|
+
var Segment = class Segment {
|
|
867
|
+
buffer;
|
|
868
|
+
view;
|
|
869
|
+
_size;
|
|
870
|
+
constructor(initialCapacity = 1024) {
|
|
871
|
+
this.buffer = new ArrayBuffer(initialCapacity);
|
|
872
|
+
this.view = new DataView(this.buffer);
|
|
873
|
+
this._size = 0;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* 从现有 buffer 创建(用于读取)
|
|
877
|
+
*/
|
|
878
|
+
static fromBuffer(buffer) {
|
|
879
|
+
const seg = new Segment(0);
|
|
880
|
+
seg.buffer = buffer;
|
|
881
|
+
seg.view = new DataView(buffer);
|
|
882
|
+
seg._size = buffer.byteLength;
|
|
883
|
+
return seg;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* 确保容量足够
|
|
887
|
+
*/
|
|
888
|
+
ensureCapacity(minBytes) {
|
|
889
|
+
if (this.buffer.byteLength >= minBytes) return;
|
|
890
|
+
let newCapacity = this.buffer.byteLength * 2;
|
|
891
|
+
while (newCapacity < minBytes) newCapacity *= 2;
|
|
892
|
+
const newBuffer = new ArrayBuffer(newCapacity);
|
|
893
|
+
new Uint8Array(newBuffer).set(new Uint8Array(this.buffer, 0, this._size));
|
|
894
|
+
this.buffer = newBuffer;
|
|
895
|
+
this.view = new DataView(newBuffer);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* 分配空间,返回字偏移
|
|
899
|
+
*/
|
|
900
|
+
allocate(words) {
|
|
901
|
+
const bytes = words * WORD_SIZE;
|
|
902
|
+
const offset = this._size;
|
|
903
|
+
this.ensureCapacity(offset + bytes);
|
|
904
|
+
this._size = offset + bytes;
|
|
905
|
+
return offset / WORD_SIZE;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* 获取字(64位)
|
|
909
|
+
*/
|
|
910
|
+
getWord(wordOffset) {
|
|
911
|
+
const byteOffset = wordOffset * WORD_SIZE;
|
|
912
|
+
if (byteOffset + 8 > this._size) throw new Error(`Offset ${wordOffset} is outside the bounds of the segment (${this.wordCount} words)`);
|
|
913
|
+
const low = BigInt(this.view.getUint32(byteOffset, true));
|
|
914
|
+
return BigInt(this.view.getUint32(byteOffset + 4, true)) << BigInt(32) | low;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* 设置字(64位)
|
|
918
|
+
*/
|
|
919
|
+
setWord(wordOffset, value) {
|
|
920
|
+
const byteOffset = wordOffset * WORD_SIZE;
|
|
921
|
+
if (byteOffset + 8 > this.buffer.byteLength) throw new Error(`Offset ${wordOffset} is outside the bounds of the segment`);
|
|
922
|
+
this.view.setUint32(byteOffset, Number(value & BigInt(4294967295)), true);
|
|
923
|
+
this.view.setUint32(byteOffset + 4, Number(value >> BigInt(32)), true);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* 获取原始 buffer(只读到 _size)
|
|
927
|
+
*/
|
|
928
|
+
asUint8Array() {
|
|
929
|
+
return new Uint8Array(this.buffer, 0, this._size);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* 获取底层 ArrayBuffer
|
|
933
|
+
*/
|
|
934
|
+
getArrayBuffer() {
|
|
935
|
+
return this.buffer;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* 获取字数量
|
|
939
|
+
*/
|
|
940
|
+
get wordCount() {
|
|
941
|
+
return this._size / WORD_SIZE;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* 获取字节数量
|
|
945
|
+
*/
|
|
946
|
+
get byteLength() {
|
|
947
|
+
return this._size;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 获取 DataView
|
|
951
|
+
*/
|
|
952
|
+
get dataView() {
|
|
953
|
+
return this.view;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
//#endregion
|
|
958
|
+
//#region src/core/list.ts
|
|
959
|
+
/**
|
|
960
|
+
* ListReader - 读取列表
|
|
961
|
+
*/
|
|
962
|
+
var ListReader = class {
|
|
963
|
+
segment;
|
|
964
|
+
startOffset;
|
|
965
|
+
segmentIndex;
|
|
966
|
+
constructor(message, segmentIndex, elementSize, elementCount, structSize, wordOffset) {
|
|
967
|
+
this.message = message;
|
|
968
|
+
this.elementSize = elementSize;
|
|
969
|
+
this.elementCount = elementCount;
|
|
970
|
+
this.structSize = structSize;
|
|
971
|
+
this.segmentIndex = segmentIndex;
|
|
972
|
+
this.segment = message.getSegment(segmentIndex);
|
|
973
|
+
this.startOffset = wordOffset;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* 列表长度
|
|
977
|
+
*/
|
|
978
|
+
get length() {
|
|
979
|
+
return this.elementCount;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* 获取元素(基础类型)
|
|
983
|
+
*/
|
|
984
|
+
getPrimitive(index) {
|
|
985
|
+
if (index < 0 || index >= this.elementCount) throw new RangeError("Index out of bounds");
|
|
986
|
+
switch (this.elementSize) {
|
|
987
|
+
case ElementSize.BIT: {
|
|
988
|
+
const byteOffset = Math.floor(index / 8);
|
|
989
|
+
const bitInByte = index % 8;
|
|
990
|
+
return (this.segment.dataView.getUint8(this.startOffset * WORD_SIZE + byteOffset) & 1 << bitInByte) !== 0 ? 1 : 0;
|
|
991
|
+
}
|
|
992
|
+
case ElementSize.BYTE: return this.segment.dataView.getUint8(this.startOffset * WORD_SIZE + index);
|
|
993
|
+
case ElementSize.TWO_BYTES: return this.segment.dataView.getUint16(this.startOffset * WORD_SIZE + index * 2, true);
|
|
994
|
+
case ElementSize.FOUR_BYTES: return this.segment.dataView.getUint32(this.startOffset * WORD_SIZE + index * 4, true);
|
|
995
|
+
case ElementSize.EIGHT_BYTES: {
|
|
996
|
+
const offset = this.startOffset * WORD_SIZE + index * 8;
|
|
997
|
+
const low = BigInt(this.segment.dataView.getUint32(offset, true));
|
|
998
|
+
return BigInt(this.segment.dataView.getUint32(offset + 4, true)) << BigInt(32) | low;
|
|
999
|
+
}
|
|
1000
|
+
default: throw new Error(`Unsupported element size: ${this.elementSize}`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* 获取结构元素
|
|
1005
|
+
*/
|
|
1006
|
+
getStruct(index) {
|
|
1007
|
+
if (!this.structSize) throw new Error("Not a struct list");
|
|
1008
|
+
const { dataWords, pointerCount } = this.structSize;
|
|
1009
|
+
const size = dataWords + pointerCount;
|
|
1010
|
+
const offset = this.startOffset + index * size;
|
|
1011
|
+
return new StructReader(this.message, this.segmentIndex, offset, dataWords, pointerCount);
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 迭代器
|
|
1015
|
+
*/
|
|
1016
|
+
*[Symbol.iterator]() {
|
|
1017
|
+
for (let i = 0; i < this.elementCount; i++) yield this.getPrimitive(i);
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
//#endregion
|
|
1022
|
+
//#region src/core/message-reader.ts
|
|
1023
|
+
/**
|
|
1024
|
+
* Cap'n Proto MessageReader
|
|
1025
|
+
* 纯 TypeScript 实现
|
|
1026
|
+
*/
|
|
1027
|
+
var MessageReader = class {
|
|
1028
|
+
segments;
|
|
1029
|
+
constructor(buffer) {
|
|
1030
|
+
const uint8Array = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
|
|
1031
|
+
this.segments = [];
|
|
1032
|
+
if (uint8Array.byteLength < 8) return;
|
|
1033
|
+
const view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
|
|
1034
|
+
const firstWordLow = view.getUint32(0, true);
|
|
1035
|
+
const firstWordHigh = view.getUint32(4, true);
|
|
1036
|
+
const segmentCount = (firstWordLow & 4294967295) + 1;
|
|
1037
|
+
const firstSegmentSize = firstWordHigh;
|
|
1038
|
+
let offset = 8;
|
|
1039
|
+
const segmentSizes = [firstSegmentSize];
|
|
1040
|
+
for (let i = 1; i < segmentCount; i++) {
|
|
1041
|
+
if (offset + 4 > uint8Array.byteLength) {
|
|
1042
|
+
this.segments = [];
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
segmentSizes.push(view.getUint32(offset, true));
|
|
1046
|
+
offset += 4;
|
|
1047
|
+
}
|
|
1048
|
+
offset = offset + 7 & -8;
|
|
1049
|
+
if (offset > uint8Array.byteLength) {
|
|
1050
|
+
this.segments = [];
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
this.segments = [];
|
|
1054
|
+
for (const size of segmentSizes) {
|
|
1055
|
+
if (offset + size * WORD_SIZE > uint8Array.byteLength) break;
|
|
1056
|
+
const segmentBuffer = uint8Array.slice(offset, offset + size * WORD_SIZE);
|
|
1057
|
+
this.segments.push(Segment.fromBuffer(segmentBuffer.buffer));
|
|
1058
|
+
offset += size * WORD_SIZE;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* 获取根结构
|
|
1063
|
+
*/
|
|
1064
|
+
getRoot(_dataWords, _pointerCount) {
|
|
1065
|
+
const segment = this.segments[0];
|
|
1066
|
+
const ptr = decodePointer(segment.getWord(0));
|
|
1067
|
+
if (ptr.tag === PointerTag.STRUCT) {
|
|
1068
|
+
const structPtr = ptr;
|
|
1069
|
+
const dataOffset = 1 + structPtr.offset;
|
|
1070
|
+
return new StructReader(this, 0, dataOffset, structPtr.dataWords, structPtr.pointerCount);
|
|
1071
|
+
}
|
|
1072
|
+
if (ptr.tag === PointerTag.FAR) {
|
|
1073
|
+
const farPtr = ptr;
|
|
1074
|
+
const targetSegment = this.getSegment(farPtr.targetSegment);
|
|
1075
|
+
if (!targetSegment) throw new Error(`Far pointer references non-existent segment ${farPtr.targetSegment}`);
|
|
1076
|
+
if (farPtr.doubleFar) {
|
|
1077
|
+
const landingPadPtr = decodePointer(targetSegment.getWord(farPtr.targetOffset));
|
|
1078
|
+
if (landingPadPtr.tag !== PointerTag.FAR) throw new Error("Double-far landing pad is not a far pointer");
|
|
1079
|
+
const innerFarPtr = landingPadPtr;
|
|
1080
|
+
const finalSegment = this.getSegment(innerFarPtr.targetSegment);
|
|
1081
|
+
if (!finalSegment) throw new Error(`Double-far references non-existent segment ${innerFarPtr.targetSegment}`);
|
|
1082
|
+
const structPtr = decodePointer(finalSegment.getWord(innerFarPtr.targetOffset));
|
|
1083
|
+
const dataOffset = innerFarPtr.targetOffset + 1 + structPtr.offset;
|
|
1084
|
+
return new StructReader(this, innerFarPtr.targetSegment, dataOffset, structPtr.dataWords, structPtr.pointerCount);
|
|
1085
|
+
}
|
|
1086
|
+
const structPtr = decodePointer(targetSegment.getWord(farPtr.targetOffset));
|
|
1087
|
+
const dataOffset = farPtr.targetOffset + 1 + structPtr.offset;
|
|
1088
|
+
return new StructReader(this, farPtr.targetSegment, dataOffset, structPtr.dataWords, structPtr.pointerCount);
|
|
1089
|
+
}
|
|
1090
|
+
throw new Error(`Root pointer is not a struct or far pointer: ${ptr.tag}`);
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* 获取段
|
|
1094
|
+
*/
|
|
1095
|
+
getSegment(index) {
|
|
1096
|
+
return this.segments[index];
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* 解析指针,处理 far pointer 间接寻址
|
|
1100
|
+
* 返回 { segmentIndex, wordOffset, pointer },其中 pointer 是实际的 struct/list 指针
|
|
1101
|
+
*/
|
|
1102
|
+
resolvePointer(segmentIndex, wordOffset) {
|
|
1103
|
+
const segment = this.getSegment(segmentIndex);
|
|
1104
|
+
if (!segment) return null;
|
|
1105
|
+
const ptrValue = segment.getWord(wordOffset);
|
|
1106
|
+
if (ptrValue === 0n) return null;
|
|
1107
|
+
const ptr = decodePointer(ptrValue);
|
|
1108
|
+
if (ptr.tag === PointerTag.STRUCT || ptr.tag === PointerTag.LIST) return {
|
|
1109
|
+
segmentIndex,
|
|
1110
|
+
wordOffset: wordOffset + 1 + ptr.offset,
|
|
1111
|
+
pointer: ptr
|
|
1112
|
+
};
|
|
1113
|
+
if (ptr.tag === PointerTag.FAR) {
|
|
1114
|
+
const farPtr = ptr;
|
|
1115
|
+
const targetSegment = this.getSegment(farPtr.targetSegment);
|
|
1116
|
+
if (!targetSegment) return null;
|
|
1117
|
+
if (farPtr.doubleFar) {
|
|
1118
|
+
const landingPadPtr = decodePointer(targetSegment.getWord(farPtr.targetOffset));
|
|
1119
|
+
if (landingPadPtr.tag !== PointerTag.FAR) return null;
|
|
1120
|
+
const innerFarPtr = landingPadPtr;
|
|
1121
|
+
const finalSegment = this.getSegment(innerFarPtr.targetSegment);
|
|
1122
|
+
if (!finalSegment) return null;
|
|
1123
|
+
const finalPtr = decodePointer(finalSegment.getWord(innerFarPtr.targetOffset));
|
|
1124
|
+
if (finalPtr.tag !== PointerTag.STRUCT && finalPtr.tag !== PointerTag.LIST) return null;
|
|
1125
|
+
const targetOffset = innerFarPtr.targetOffset + 1 + finalPtr.offset;
|
|
1126
|
+
return {
|
|
1127
|
+
segmentIndex: innerFarPtr.targetSegment,
|
|
1128
|
+
wordOffset: targetOffset,
|
|
1129
|
+
pointer: finalPtr
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
const landingPadPtr = decodePointer(targetSegment.getWord(farPtr.targetOffset));
|
|
1133
|
+
if (landingPadPtr.tag !== PointerTag.STRUCT && landingPadPtr.tag !== PointerTag.LIST) return null;
|
|
1134
|
+
const targetOffset = farPtr.targetOffset + 1 + landingPadPtr.offset;
|
|
1135
|
+
return {
|
|
1136
|
+
segmentIndex: farPtr.targetSegment,
|
|
1137
|
+
wordOffset: targetOffset,
|
|
1138
|
+
pointer: landingPadPtr
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* 段数量
|
|
1145
|
+
*/
|
|
1146
|
+
get segmentCount() {
|
|
1147
|
+
return this.segments.length;
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
/**
|
|
1151
|
+
* 结构读取器
|
|
1152
|
+
*/
|
|
1153
|
+
var StructReader = class StructReader {
|
|
1154
|
+
constructor(message, segmentIndex, wordOffset, dataWords, pointerCount) {
|
|
1155
|
+
this.message = message;
|
|
1156
|
+
this.segmentIndex = segmentIndex;
|
|
1157
|
+
this.wordOffset = wordOffset;
|
|
1158
|
+
this.dataWords = dataWords;
|
|
1159
|
+
this.pointerCount = pointerCount;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* 获取 bool 字段
|
|
1163
|
+
*/
|
|
1164
|
+
getBool(bitOffset) {
|
|
1165
|
+
const byteOffset = Math.floor(bitOffset / 8);
|
|
1166
|
+
const bitInByte = bitOffset % 8;
|
|
1167
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1168
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1169
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1170
|
+
if (dataOffset < 0 || dataOffset >= dataSectionEnd) return false;
|
|
1171
|
+
return (segment.dataView.getUint8(dataOffset) & 1 << bitInByte) !== 0;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* 获取 int8 字段
|
|
1175
|
+
*/
|
|
1176
|
+
getInt8(byteOffset) {
|
|
1177
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1178
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1179
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1180
|
+
if (dataOffset < 0 || dataOffset >= dataSectionEnd) return 0;
|
|
1181
|
+
return segment.dataView.getInt8(dataOffset);
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* 获取 int16 字段
|
|
1185
|
+
*/
|
|
1186
|
+
getInt16(byteOffset) {
|
|
1187
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1188
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1189
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1190
|
+
if (dataOffset < 0 || dataOffset + 2 > dataSectionEnd) return 0;
|
|
1191
|
+
return segment.dataView.getInt16(dataOffset, true);
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* 获取 int32 字段
|
|
1195
|
+
*/
|
|
1196
|
+
getInt32(byteOffset) {
|
|
1197
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1198
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1199
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1200
|
+
if (dataOffset < 0 || dataOffset + 4 > dataSectionEnd) return 0;
|
|
1201
|
+
return segment.dataView.getInt32(dataOffset, true);
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* 获取 int64 字段
|
|
1205
|
+
*/
|
|
1206
|
+
getInt64(byteOffset) {
|
|
1207
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1208
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1209
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1210
|
+
if (dataOffset < 0 || dataOffset + 8 > dataSectionEnd) return BigInt(0);
|
|
1211
|
+
const low = BigInt(segment.dataView.getUint32(dataOffset, true));
|
|
1212
|
+
return BigInt(segment.dataView.getInt32(dataOffset + 4, true)) << BigInt(32) | low;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* 获取 uint8 字段
|
|
1216
|
+
*/
|
|
1217
|
+
getUint8(byteOffset) {
|
|
1218
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1219
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1220
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1221
|
+
if (dataOffset < 0 || dataOffset >= dataSectionEnd) return 0;
|
|
1222
|
+
return segment.dataView.getUint8(dataOffset);
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* 获取 uint16 字段
|
|
1226
|
+
*/
|
|
1227
|
+
getUint16(byteOffset) {
|
|
1228
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1229
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1230
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1231
|
+
if (dataOffset < 0 || dataOffset + 2 > dataSectionEnd) return 0;
|
|
1232
|
+
return segment.dataView.getUint16(dataOffset, true);
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* 获取 uint32 字段
|
|
1236
|
+
*/
|
|
1237
|
+
getUint32(byteOffset) {
|
|
1238
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1239
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1240
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1241
|
+
if (dataOffset < 0 || dataOffset + 4 > dataSectionEnd) return 0;
|
|
1242
|
+
return segment.dataView.getUint32(dataOffset, true);
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* 获取 uint64 字段
|
|
1246
|
+
*/
|
|
1247
|
+
getUint64(byteOffset) {
|
|
1248
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1249
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1250
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1251
|
+
if (dataOffset < 0 || dataOffset + 8 > dataSectionEnd) return BigInt(0);
|
|
1252
|
+
const low = BigInt(segment.dataView.getUint32(dataOffset, true));
|
|
1253
|
+
return BigInt(segment.dataView.getUint32(dataOffset + 4, true)) << BigInt(32) | low;
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* 获取 float32 字段
|
|
1257
|
+
*/
|
|
1258
|
+
getFloat32(byteOffset) {
|
|
1259
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1260
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1261
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1262
|
+
if (dataOffset < 0 || dataOffset + 4 > dataSectionEnd) return 0;
|
|
1263
|
+
return segment.dataView.getFloat32(dataOffset, true);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* 获取 float64 字段
|
|
1267
|
+
*/
|
|
1268
|
+
getFloat64(byteOffset) {
|
|
1269
|
+
const segment = this.message.getSegment(this.segmentIndex);
|
|
1270
|
+
const dataOffset = this.wordOffset * WORD_SIZE + byteOffset;
|
|
1271
|
+
const dataSectionEnd = this.wordOffset * WORD_SIZE + this.dataWords * WORD_SIZE;
|
|
1272
|
+
if (dataOffset < 0 || dataOffset + 8 > dataSectionEnd) return 0;
|
|
1273
|
+
return segment.dataView.getFloat64(dataOffset, true);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* 获取文本字段
|
|
1277
|
+
*/
|
|
1278
|
+
getText(pointerIndex) {
|
|
1279
|
+
const ptrOffset = this.wordOffset + this.dataWords + pointerIndex;
|
|
1280
|
+
const resolved = this.message.resolvePointer(this.segmentIndex, ptrOffset);
|
|
1281
|
+
if (!resolved) return "";
|
|
1282
|
+
const { segmentIndex, wordOffset, pointer } = resolved;
|
|
1283
|
+
if (pointer.tag !== PointerTag.LIST) return "";
|
|
1284
|
+
const listPtr = pointer;
|
|
1285
|
+
const segment = this.message.getSegment(segmentIndex);
|
|
1286
|
+
const byteLength = listPtr.elementCount > 0 ? listPtr.elementCount - 1 : 0;
|
|
1287
|
+
if (byteLength === 0) return "";
|
|
1288
|
+
const bytes = new Uint8Array(segment.dataView.buffer, wordOffset * WORD_SIZE, byteLength);
|
|
1289
|
+
return new TextDecoder().decode(bytes);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* 获取嵌套结构
|
|
1293
|
+
*/
|
|
1294
|
+
getStruct(pointerIndex, _dataWords, _pointerCount) {
|
|
1295
|
+
const ptrOffset = this.wordOffset + this.dataWords + pointerIndex;
|
|
1296
|
+
const resolved = this.message.resolvePointer(this.segmentIndex, ptrOffset);
|
|
1297
|
+
if (!resolved) return void 0;
|
|
1298
|
+
const { segmentIndex, wordOffset, pointer } = resolved;
|
|
1299
|
+
if (pointer.tag !== PointerTag.STRUCT) return void 0;
|
|
1300
|
+
const structPtr = pointer;
|
|
1301
|
+
return new StructReader(this.message, segmentIndex, wordOffset, structPtr.dataWords, structPtr.pointerCount);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* 获取列表
|
|
1305
|
+
*/
|
|
1306
|
+
getList(pointerIndex, _elementSize, structSize) {
|
|
1307
|
+
const ptrOffset = this.wordOffset + this.dataWords + pointerIndex;
|
|
1308
|
+
const resolved = this.message.resolvePointer(this.segmentIndex, ptrOffset);
|
|
1309
|
+
if (!resolved) return void 0;
|
|
1310
|
+
const { segmentIndex, wordOffset, pointer } = resolved;
|
|
1311
|
+
if (pointer.tag !== PointerTag.LIST) return void 0;
|
|
1312
|
+
const listPtr = pointer;
|
|
1313
|
+
let targetOffset = wordOffset;
|
|
1314
|
+
let elementCount = listPtr.elementCount;
|
|
1315
|
+
let actualStructSize = structSize;
|
|
1316
|
+
const segment = this.message.getSegment(segmentIndex);
|
|
1317
|
+
if (listPtr.elementSize === ElementSize.COMPOSITE) {
|
|
1318
|
+
if (targetOffset < 0 || targetOffset >= segment.wordCount) return;
|
|
1319
|
+
try {
|
|
1320
|
+
const tagWord = segment.getWord(targetOffset);
|
|
1321
|
+
elementCount = Number(tagWord & BigInt(4294967295));
|
|
1322
|
+
actualStructSize = {
|
|
1323
|
+
dataWords: Number(tagWord >> BigInt(32) & BigInt(65535)),
|
|
1324
|
+
pointerCount: Number(tagWord >> BigInt(48) & BigInt(65535))
|
|
1325
|
+
};
|
|
1326
|
+
targetOffset += 1;
|
|
1327
|
+
} catch {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return new ListReader(this.message, segmentIndex, listPtr.elementSize, elementCount, actualStructSize, targetOffset);
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
//#endregion
|
|
1336
|
+
//#region src/schema/schema-reader.ts
|
|
1337
|
+
/**
|
|
1338
|
+
* Cap'n Proto 编译后 Schema 的 TypeScript Reader
|
|
1339
|
+
* 基于官方 schema.capnp 手动编写
|
|
238
1340
|
*
|
|
239
|
-
*
|
|
240
|
-
* - Struct 定义
|
|
241
|
-
* - Enum 定义
|
|
242
|
-
* - 基础类型字段
|
|
243
|
-
* - List 类型
|
|
244
|
-
* - 嵌套 struct 引用
|
|
1341
|
+
* 这是自举的基础:我们需要先能读取 schema 才能生成 schema 的代码
|
|
245
1342
|
*
|
|
246
|
-
*
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
1343
|
+
* 结构信息来自: capnp compile -ocapnp schema.capnp
|
|
1344
|
+
*/
|
|
1345
|
+
var NodeReader = class {
|
|
1346
|
+
constructor(reader) {
|
|
1347
|
+
this.reader = reader;
|
|
1348
|
+
}
|
|
1349
|
+
get id() {
|
|
1350
|
+
return this.reader.getUint64(0);
|
|
1351
|
+
}
|
|
1352
|
+
get displayName() {
|
|
1353
|
+
return this.reader.getText(0);
|
|
1354
|
+
}
|
|
1355
|
+
get displayNamePrefixLength() {
|
|
1356
|
+
return this.reader.getUint32(8);
|
|
1357
|
+
}
|
|
1358
|
+
get scopeId() {
|
|
1359
|
+
return this.reader.getUint64(16);
|
|
1360
|
+
}
|
|
1361
|
+
/** union tag 在 bits [96, 112) = bytes [12, 14) */
|
|
1362
|
+
get unionTag() {
|
|
1363
|
+
return this.reader.getUint16(12);
|
|
1364
|
+
}
|
|
1365
|
+
get isFile() {
|
|
1366
|
+
return this.unionTag === 0;
|
|
1367
|
+
}
|
|
1368
|
+
get isStruct() {
|
|
1369
|
+
return this.unionTag === 1;
|
|
1370
|
+
}
|
|
1371
|
+
get isEnum() {
|
|
1372
|
+
return this.unionTag === 2;
|
|
1373
|
+
}
|
|
1374
|
+
get isInterface() {
|
|
1375
|
+
return this.unionTag === 3;
|
|
1376
|
+
}
|
|
1377
|
+
get isConst() {
|
|
1378
|
+
return this.unionTag === 4;
|
|
1379
|
+
}
|
|
1380
|
+
get isAnnotation() {
|
|
1381
|
+
return this.unionTag === 5;
|
|
1382
|
+
}
|
|
1383
|
+
get structDataWordCount() {
|
|
1384
|
+
return this.reader.getUint16(14);
|
|
1385
|
+
}
|
|
1386
|
+
get structPointerCount() {
|
|
1387
|
+
return this.reader.getUint16(24);
|
|
1388
|
+
}
|
|
1389
|
+
get structPreferredListEncoding() {
|
|
1390
|
+
return this.reader.getUint16(26);
|
|
1391
|
+
}
|
|
1392
|
+
get structIsGroup() {
|
|
1393
|
+
return this.reader.getBool(224);
|
|
1394
|
+
}
|
|
1395
|
+
get structDiscriminantCount() {
|
|
1396
|
+
return this.reader.getUint16(30);
|
|
1397
|
+
}
|
|
1398
|
+
get structDiscriminantOffset() {
|
|
1399
|
+
return this.reader.getUint32(32);
|
|
1400
|
+
}
|
|
1401
|
+
get structFields() {
|
|
1402
|
+
const listReader = this.reader.getList(3, ElementSize.INLINE_COMPOSITE, {
|
|
1403
|
+
dataWords: 3,
|
|
1404
|
+
pointerCount: 4
|
|
267
1405
|
});
|
|
1406
|
+
if (!listReader) return [];
|
|
1407
|
+
const fields = [];
|
|
1408
|
+
for (let i = 0; i < listReader.length; i++) {
|
|
1409
|
+
const field = new FieldReader(listReader.getStruct(i));
|
|
1410
|
+
if (!field.name && i > 0) break;
|
|
1411
|
+
fields.push(field);
|
|
1412
|
+
}
|
|
1413
|
+
return fields;
|
|
268
1414
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const values = parseEnumValues(body);
|
|
274
|
-
enums.push({
|
|
275
|
-
name,
|
|
276
|
-
values
|
|
1415
|
+
get enumEnumerants() {
|
|
1416
|
+
const listReader = this.reader.getList(3, ElementSize.INLINE_COMPOSITE, {
|
|
1417
|
+
dataWords: 1,
|
|
1418
|
+
pointerCount: 2
|
|
277
1419
|
});
|
|
1420
|
+
if (!listReader) return [];
|
|
1421
|
+
const enumerants = [];
|
|
1422
|
+
for (let i = 0; i < Math.min(listReader.length, 10); i++) try {
|
|
1423
|
+
const enumerant = new EnumerantReader(listReader.getStruct(i));
|
|
1424
|
+
if (!enumerant.name) break;
|
|
1425
|
+
enumerants.push(enumerant);
|
|
1426
|
+
} catch (_e) {
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
return enumerants;
|
|
278
1430
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
function parseFields(body) {
|
|
285
|
-
const fields = [];
|
|
286
|
-
const fieldRegex = /(\w+)\s*@(\d+)\s*:\s*([^;]+);/g;
|
|
287
|
-
let match;
|
|
288
|
-
while ((match = fieldRegex.exec(body)) !== null) {
|
|
289
|
-
const name = match[1];
|
|
290
|
-
const index = Number.parseInt(match[2]);
|
|
291
|
-
const type = parseType(match[3].trim());
|
|
292
|
-
fields.push({
|
|
293
|
-
name,
|
|
294
|
-
index,
|
|
295
|
-
type
|
|
1431
|
+
get interfaceMethods() {
|
|
1432
|
+
const listReader = this.reader.getList(3, ElementSize.INLINE_COMPOSITE, {
|
|
1433
|
+
dataWords: 5,
|
|
1434
|
+
pointerCount: 2
|
|
296
1435
|
});
|
|
1436
|
+
if (!listReader) return [];
|
|
1437
|
+
const methods = [];
|
|
1438
|
+
for (let i = 0; i < listReader.length; i++) try {
|
|
1439
|
+
const method = new MethodReader(listReader.getStruct(i));
|
|
1440
|
+
if (!method.name && i > 0) break;
|
|
1441
|
+
methods.push(method);
|
|
1442
|
+
} catch (_e) {
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
1445
|
+
return methods;
|
|
297
1446
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (listMatch) return {
|
|
303
|
-
kind: "list",
|
|
304
|
-
elementType: parseType(listMatch[1].trim())
|
|
305
|
-
};
|
|
306
|
-
if (isPrimitive(typeStr)) return typeStr;
|
|
307
|
-
return {
|
|
308
|
-
kind: "struct",
|
|
309
|
-
name: typeStr
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
function parseEnumValues(body) {
|
|
313
|
-
const values = [];
|
|
314
|
-
const valueRegex = /(\w+)\s*@(\d+)\s*;/g;
|
|
315
|
-
let match;
|
|
316
|
-
while ((match = valueRegex.exec(body)) !== null) {
|
|
317
|
-
const name = match[1];
|
|
318
|
-
const index = Number.parseInt(match[2]);
|
|
319
|
-
values.push({
|
|
320
|
-
name,
|
|
321
|
-
index
|
|
1447
|
+
get nestedNodes() {
|
|
1448
|
+
const listReader = this.reader.getList(1, ElementSize.INLINE_COMPOSITE, {
|
|
1449
|
+
dataWords: 1,
|
|
1450
|
+
pointerCount: 1
|
|
322
1451
|
});
|
|
1452
|
+
if (!listReader) return [];
|
|
1453
|
+
const nodes = [];
|
|
1454
|
+
for (let i = 0; i < listReader.length; i++) {
|
|
1455
|
+
const itemReader = listReader.getStruct(i);
|
|
1456
|
+
nodes.push(new NestedNodeReader(itemReader));
|
|
1457
|
+
}
|
|
1458
|
+
return nodes;
|
|
323
1459
|
}
|
|
324
|
-
|
|
325
|
-
|
|
1460
|
+
};
|
|
1461
|
+
var FieldReader = class {
|
|
1462
|
+
constructor(reader) {
|
|
1463
|
+
this.reader = reader;
|
|
1464
|
+
}
|
|
1465
|
+
get name() {
|
|
1466
|
+
return this.reader.getText(0);
|
|
1467
|
+
}
|
|
1468
|
+
get codeOrder() {
|
|
1469
|
+
return this.reader.getUint16(0);
|
|
1470
|
+
}
|
|
1471
|
+
get discriminantValue() {
|
|
1472
|
+
return this.reader.getUint16(2);
|
|
1473
|
+
}
|
|
1474
|
+
/** union tag 在 bits [64, 80) = bytes [8, 10) */
|
|
1475
|
+
get unionTag() {
|
|
1476
|
+
return this.reader.getUint16(8);
|
|
1477
|
+
}
|
|
1478
|
+
get isSlot() {
|
|
1479
|
+
return this.unionTag === 0;
|
|
1480
|
+
}
|
|
1481
|
+
get isGroup() {
|
|
1482
|
+
return this.unionTag === 1;
|
|
1483
|
+
}
|
|
1484
|
+
get slotOffset() {
|
|
1485
|
+
return this.reader.getUint32(4);
|
|
1486
|
+
}
|
|
1487
|
+
get slotType() {
|
|
1488
|
+
const typeReader = this.reader.getStruct(2, 3, 1);
|
|
1489
|
+
return typeReader ? new TypeReader(typeReader) : null;
|
|
1490
|
+
}
|
|
1491
|
+
get groupTypeId() {
|
|
1492
|
+
return this.reader.getUint64(16);
|
|
1493
|
+
}
|
|
1494
|
+
get defaultValue() {
|
|
1495
|
+
const valueReader = this.reader.getStruct(3, 2, 1);
|
|
1496
|
+
return valueReader ? new ValueReader(valueReader) : null;
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
var TypeReader = class TypeReader {
|
|
1500
|
+
constructor(reader) {
|
|
1501
|
+
this.reader = reader;
|
|
1502
|
+
}
|
|
1503
|
+
get kind() {
|
|
1504
|
+
return TYPE_KIND_MAP[this.reader.getUint16(0)] ?? "unknown";
|
|
1505
|
+
}
|
|
1506
|
+
get isPrimitive() {
|
|
1507
|
+
const k = this.kind;
|
|
1508
|
+
return k !== "list" && k !== "enum" && k !== "struct" && k !== "interface" && k !== "anyPointer";
|
|
1509
|
+
}
|
|
1510
|
+
get listElementType() {
|
|
1511
|
+
if (this.kind !== "list") return null;
|
|
1512
|
+
const elementReader = this.reader.getStruct(0, 3, 1);
|
|
1513
|
+
return elementReader ? new TypeReader(elementReader) : null;
|
|
1514
|
+
}
|
|
1515
|
+
get typeId() {
|
|
1516
|
+
const k = this.kind;
|
|
1517
|
+
if (k === "enum" || k === "struct" || k === "interface") return this.reader.getUint64(8);
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
const TYPE_KIND_MAP = {
|
|
1522
|
+
0: "void",
|
|
1523
|
+
1: "bool",
|
|
1524
|
+
2: "int8",
|
|
1525
|
+
3: "int16",
|
|
1526
|
+
4: "int32",
|
|
1527
|
+
5: "int64",
|
|
1528
|
+
6: "uint8",
|
|
1529
|
+
7: "uint16",
|
|
1530
|
+
8: "uint32",
|
|
1531
|
+
9: "uint64",
|
|
1532
|
+
10: "float32",
|
|
1533
|
+
11: "float64",
|
|
1534
|
+
12: "text",
|
|
1535
|
+
13: "data",
|
|
1536
|
+
14: "list",
|
|
1537
|
+
15: "enum",
|
|
1538
|
+
16: "struct",
|
|
1539
|
+
17: "interface",
|
|
1540
|
+
18: "anyPointer"
|
|
1541
|
+
};
|
|
1542
|
+
var ValueReader = class {
|
|
1543
|
+
constructor(reader) {
|
|
1544
|
+
this.reader = reader;
|
|
1545
|
+
}
|
|
1546
|
+
/** union tag 在 bits [0, 16) = bytes [0, 2) */
|
|
1547
|
+
get unionTag() {
|
|
1548
|
+
return this.reader.getUint16(0);
|
|
1549
|
+
}
|
|
1550
|
+
get isVoid() {
|
|
1551
|
+
return this.unionTag === 0;
|
|
1552
|
+
}
|
|
1553
|
+
get isBool() {
|
|
1554
|
+
return this.unionTag === 1;
|
|
1555
|
+
}
|
|
1556
|
+
get isInt8() {
|
|
1557
|
+
return this.unionTag === 2;
|
|
1558
|
+
}
|
|
1559
|
+
get isInt16() {
|
|
1560
|
+
return this.unionTag === 3;
|
|
1561
|
+
}
|
|
1562
|
+
get isInt32() {
|
|
1563
|
+
return this.unionTag === 4;
|
|
1564
|
+
}
|
|
1565
|
+
get isInt64() {
|
|
1566
|
+
return this.unionTag === 5;
|
|
1567
|
+
}
|
|
1568
|
+
get isUint8() {
|
|
1569
|
+
return this.unionTag === 6;
|
|
1570
|
+
}
|
|
1571
|
+
get isUint16() {
|
|
1572
|
+
return this.unionTag === 7;
|
|
1573
|
+
}
|
|
1574
|
+
get isUint32() {
|
|
1575
|
+
return this.unionTag === 8;
|
|
1576
|
+
}
|
|
1577
|
+
get isUint64() {
|
|
1578
|
+
return this.unionTag === 9;
|
|
1579
|
+
}
|
|
1580
|
+
get isFloat32() {
|
|
1581
|
+
return this.unionTag === 10;
|
|
1582
|
+
}
|
|
1583
|
+
get isFloat64() {
|
|
1584
|
+
return this.unionTag === 11;
|
|
1585
|
+
}
|
|
1586
|
+
get isText() {
|
|
1587
|
+
return this.unionTag === 12;
|
|
1588
|
+
}
|
|
1589
|
+
get isData() {
|
|
1590
|
+
return this.unionTag === 13;
|
|
1591
|
+
}
|
|
1592
|
+
get isList() {
|
|
1593
|
+
return this.unionTag === 14;
|
|
1594
|
+
}
|
|
1595
|
+
get isEnum() {
|
|
1596
|
+
return this.unionTag === 15;
|
|
1597
|
+
}
|
|
1598
|
+
get isStruct() {
|
|
1599
|
+
return this.unionTag === 16;
|
|
1600
|
+
}
|
|
1601
|
+
get isInterface() {
|
|
1602
|
+
return this.unionTag === 17;
|
|
1603
|
+
}
|
|
1604
|
+
get isAnyPointer() {
|
|
1605
|
+
return this.unionTag === 18;
|
|
1606
|
+
}
|
|
1607
|
+
get boolValue() {
|
|
1608
|
+
return this.reader.getBool(16);
|
|
1609
|
+
}
|
|
1610
|
+
get int8Value() {
|
|
1611
|
+
return this.reader.getInt8(2);
|
|
1612
|
+
}
|
|
1613
|
+
get int16Value() {
|
|
1614
|
+
return this.reader.getInt16(2);
|
|
1615
|
+
}
|
|
1616
|
+
get int32Value() {
|
|
1617
|
+
return this.reader.getInt32(4);
|
|
1618
|
+
}
|
|
1619
|
+
get int64Value() {
|
|
1620
|
+
return this.reader.getInt64(8);
|
|
1621
|
+
}
|
|
1622
|
+
get uint8Value() {
|
|
1623
|
+
return this.reader.getUint8(2);
|
|
1624
|
+
}
|
|
1625
|
+
get uint16Value() {
|
|
1626
|
+
return this.reader.getUint16(2);
|
|
1627
|
+
}
|
|
1628
|
+
get uint32Value() {
|
|
1629
|
+
return this.reader.getUint32(4);
|
|
1630
|
+
}
|
|
1631
|
+
get uint64Value() {
|
|
1632
|
+
return this.reader.getUint64(8);
|
|
1633
|
+
}
|
|
1634
|
+
get float32Value() {
|
|
1635
|
+
return this.reader.getFloat32(4);
|
|
1636
|
+
}
|
|
1637
|
+
get float64Value() {
|
|
1638
|
+
return this.reader.getFloat64(8);
|
|
1639
|
+
}
|
|
1640
|
+
get enumValue() {
|
|
1641
|
+
return this.reader.getUint16(2);
|
|
1642
|
+
}
|
|
1643
|
+
getAsNumber() {
|
|
1644
|
+
if (this.isInt8) return this.int8Value;
|
|
1645
|
+
if (this.isInt16) return this.int16Value;
|
|
1646
|
+
if (this.isInt32) return this.int32Value;
|
|
1647
|
+
if (this.isUint8) return this.uint8Value;
|
|
1648
|
+
if (this.isUint16) return this.uint16Value;
|
|
1649
|
+
if (this.isUint32) return this.uint32Value;
|
|
1650
|
+
if (this.isFloat32) return this.float32Value;
|
|
1651
|
+
if (this.isFloat64) return this.float64Value;
|
|
1652
|
+
if (this.isEnum) return this.enumValue;
|
|
1653
|
+
}
|
|
1654
|
+
getAsBigint() {
|
|
1655
|
+
if (this.isInt64) return this.int64Value;
|
|
1656
|
+
if (this.isUint64) return this.uint64Value;
|
|
1657
|
+
}
|
|
1658
|
+
getAsBoolean() {
|
|
1659
|
+
if (this.isBool) return this.boolValue;
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
var MethodReader = class {
|
|
1663
|
+
constructor(reader) {
|
|
1664
|
+
this.reader = reader;
|
|
1665
|
+
}
|
|
1666
|
+
get name() {
|
|
1667
|
+
return this.reader.getText(0);
|
|
1668
|
+
}
|
|
1669
|
+
get codeOrder() {
|
|
1670
|
+
return this.reader.getUint16(0);
|
|
1671
|
+
}
|
|
1672
|
+
get paramStructType() {
|
|
1673
|
+
return this.reader.getUint64(8);
|
|
1674
|
+
}
|
|
1675
|
+
get resultStructType() {
|
|
1676
|
+
return this.reader.getUint64(16);
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
var EnumerantReader = class {
|
|
1680
|
+
constructor(reader) {
|
|
1681
|
+
this.reader = reader;
|
|
1682
|
+
}
|
|
1683
|
+
get name() {
|
|
1684
|
+
return this.reader.getText(0);
|
|
1685
|
+
}
|
|
1686
|
+
get codeOrder() {
|
|
1687
|
+
return this.reader.getUint16(0);
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
var NestedNodeReader = class {
|
|
1691
|
+
constructor(reader) {
|
|
1692
|
+
this.reader = reader;
|
|
1693
|
+
}
|
|
1694
|
+
get name() {
|
|
1695
|
+
return this.reader.getText(0);
|
|
1696
|
+
}
|
|
1697
|
+
get id() {
|
|
1698
|
+
return this.reader.getUint64(0);
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
var CodeGeneratorRequestReader = class CodeGeneratorRequestReader {
|
|
1702
|
+
constructor(message) {
|
|
1703
|
+
this.message = message;
|
|
1704
|
+
}
|
|
1705
|
+
static fromBuffer(buffer) {
|
|
1706
|
+
return new CodeGeneratorRequestReader(new MessageReader(buffer));
|
|
1707
|
+
}
|
|
1708
|
+
get nodes() {
|
|
1709
|
+
const listReader = this.message.getRoot(0, 4).getList(0, ElementSize.INLINE_COMPOSITE, {
|
|
1710
|
+
dataWords: 6,
|
|
1711
|
+
pointerCount: 6
|
|
1712
|
+
});
|
|
1713
|
+
if (!listReader) return [];
|
|
1714
|
+
const nodes = [];
|
|
1715
|
+
for (let i = 0; i < listReader.length; i++) {
|
|
1716
|
+
const nodeReader = listReader.getStruct(i);
|
|
1717
|
+
nodes.push(new NodeReader(nodeReader));
|
|
1718
|
+
}
|
|
1719
|
+
return nodes;
|
|
1720
|
+
}
|
|
1721
|
+
get requestedFiles() {
|
|
1722
|
+
const listReader = this.message.getRoot(0, 4).getList(1, ElementSize.INLINE_COMPOSITE, {
|
|
1723
|
+
dataWords: 1,
|
|
1724
|
+
pointerCount: 2
|
|
1725
|
+
});
|
|
1726
|
+
if (!listReader) return [];
|
|
1727
|
+
const files = [];
|
|
1728
|
+
for (let i = 0; i < listReader.length; i++) try {
|
|
1729
|
+
const file = new RequestedFileReader(listReader.getStruct(i));
|
|
1730
|
+
if (!file.filename && files.length > 0) break;
|
|
1731
|
+
files.push(file);
|
|
1732
|
+
} catch (_e) {
|
|
1733
|
+
break;
|
|
1734
|
+
}
|
|
1735
|
+
return files;
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
var RequestedFileReader = class {
|
|
1739
|
+
constructor(reader) {
|
|
1740
|
+
this.reader = reader;
|
|
1741
|
+
}
|
|
1742
|
+
get id() {
|
|
1743
|
+
return this.reader.getUint64(0);
|
|
1744
|
+
}
|
|
1745
|
+
get filename() {
|
|
1746
|
+
return this.reader.getText(0);
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
326
1749
|
|
|
327
1750
|
//#endregion
|
|
328
1751
|
//#region src/cli.ts
|
|
@@ -339,55 +1762,514 @@ const { values, positionals } = parseArgs({
|
|
|
339
1762
|
type: "string",
|
|
340
1763
|
short: "o"
|
|
341
1764
|
},
|
|
1765
|
+
outDir: {
|
|
1766
|
+
type: "string",
|
|
1767
|
+
short: "d"
|
|
1768
|
+
},
|
|
1769
|
+
runtimePath: {
|
|
1770
|
+
type: "string",
|
|
1771
|
+
short: "r"
|
|
1772
|
+
},
|
|
1773
|
+
dynamic: {
|
|
1774
|
+
type: "boolean",
|
|
1775
|
+
short: "D"
|
|
1776
|
+
},
|
|
1777
|
+
interactive: {
|
|
1778
|
+
type: "boolean",
|
|
1779
|
+
short: "i"
|
|
1780
|
+
},
|
|
342
1781
|
help: {
|
|
343
1782
|
type: "boolean",
|
|
344
1783
|
short: "h"
|
|
1784
|
+
},
|
|
1785
|
+
version: {
|
|
1786
|
+
type: "boolean",
|
|
1787
|
+
short: "v"
|
|
345
1788
|
}
|
|
346
1789
|
},
|
|
347
1790
|
allowPositionals: true
|
|
348
1791
|
});
|
|
1792
|
+
const VERSION = "3.0.0";
|
|
1793
|
+
if (values.version) {
|
|
1794
|
+
console.log(VERSION);
|
|
1795
|
+
process.exit(0);
|
|
1796
|
+
}
|
|
349
1797
|
if (values.help || positionals.length === 0) {
|
|
350
1798
|
console.log(`
|
|
351
|
-
Cap'n Proto TypeScript
|
|
1799
|
+
Cap'n Proto TypeScript Code Generator v3
|
|
352
1800
|
|
|
353
|
-
Usage:
|
|
354
|
-
capnp gen <schema.capnp> [options] Generate TypeScript from schema
|
|
355
|
-
capnp --help Show this help
|
|
1801
|
+
Usage: capnp-ts-codegen <schema.capnp> [options]
|
|
356
1802
|
|
|
357
1803
|
Options:
|
|
358
|
-
-o, --output
|
|
359
|
-
-
|
|
1804
|
+
-o, --output Output file (default: stdout for single file)
|
|
1805
|
+
-d, --outDir Output directory for multiple files
|
|
1806
|
+
-r, --runtimePath Runtime library import path (default: @naeemo/capnp)
|
|
1807
|
+
-D, --dynamic Generate dynamic schema loading code
|
|
1808
|
+
-i, --interactive Start interactive schema query tool
|
|
1809
|
+
-h, --help Show this help
|
|
1810
|
+
-v, --version Show version
|
|
360
1811
|
|
|
361
1812
|
Examples:
|
|
362
|
-
|
|
363
|
-
capnp
|
|
1813
|
+
capnp-ts-codegen schema.capnp -o schema.ts
|
|
1814
|
+
capnp-ts-codegen schema.capnp -d ./generated
|
|
1815
|
+
capnp-ts-codegen schema.capnp -o schema.ts -r ../runtime
|
|
1816
|
+
capnp-ts-codegen schema.capnp -D -o schema-dynamic.ts
|
|
1817
|
+
capnp-ts-codegen schema.capnp -i
|
|
1818
|
+
|
|
1819
|
+
Features:
|
|
1820
|
+
- Struct with all primitive types
|
|
1821
|
+
- Enum
|
|
1822
|
+
- List<T>
|
|
1823
|
+
- Text, Data
|
|
1824
|
+
- Nested struct references
|
|
1825
|
+
- Union (discriminant handling)
|
|
1826
|
+
- Group
|
|
1827
|
+
- Default values (XOR encoding)
|
|
1828
|
+
- Multi-segment messages
|
|
1829
|
+
- Interface (RPC client/server generation)
|
|
1830
|
+
- Dynamic schema loading (Phase 7)
|
|
1831
|
+
|
|
1832
|
+
Not yet supported:
|
|
1833
|
+
- Const
|
|
1834
|
+
- Advanced RPC features (Level 2-4)
|
|
364
1835
|
`);
|
|
365
1836
|
process.exit(0);
|
|
366
1837
|
}
|
|
367
|
-
const
|
|
368
|
-
if (
|
|
369
|
-
console.error(`
|
|
370
|
-
console.error("Run \"capnp --help\" for usage");
|
|
1838
|
+
const inputFile = positionals[0];
|
|
1839
|
+
if (!existsSync(inputFile)) {
|
|
1840
|
+
console.error(`Error: File not found: ${inputFile}`);
|
|
371
1841
|
process.exit(1);
|
|
372
1842
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
1843
|
+
function checkCapnpTool() {
|
|
1844
|
+
try {
|
|
1845
|
+
execSync("capnp --version", { stdio: "ignore" });
|
|
1846
|
+
return true;
|
|
1847
|
+
} catch {
|
|
1848
|
+
return false;
|
|
1849
|
+
}
|
|
378
1850
|
}
|
|
379
|
-
const outputFile = values.output;
|
|
380
1851
|
async function main() {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
1852
|
+
if (values.interactive) {
|
|
1853
|
+
await runInteractiveMode(inputFile);
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
if (!checkCapnpTool()) {
|
|
1857
|
+
console.error("Error: capnp tool not found. Please install Cap'n Proto.");
|
|
1858
|
+
console.error(" macOS: brew install capnp");
|
|
1859
|
+
console.error(" Ubuntu/Debian: apt-get install capnproto");
|
|
1860
|
+
console.error(" Other: https://capnproto.org/install.html");
|
|
1861
|
+
process.exit(1);
|
|
1862
|
+
}
|
|
1863
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "capnp-ts-"));
|
|
1864
|
+
const binFile = join(tmpDir, "schema.bin");
|
|
1865
|
+
try {
|
|
1866
|
+
console.error(`Compiling ${inputFile}...`);
|
|
1867
|
+
const inputDir = dirname(inputFile);
|
|
1868
|
+
execSync(`capnp compile -o- "${inputFile}" > "${binFile}"`, {
|
|
1869
|
+
cwd: inputDir,
|
|
1870
|
+
stdio: [
|
|
1871
|
+
"ignore",
|
|
1872
|
+
"ignore",
|
|
1873
|
+
"pipe"
|
|
1874
|
+
]
|
|
1875
|
+
});
|
|
1876
|
+
console.error("Reading binary schema...");
|
|
1877
|
+
const buffer = readFileSync(binFile);
|
|
1878
|
+
const arrayBuffer = new Uint8Array(buffer.byteLength);
|
|
1879
|
+
arrayBuffer.set(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength));
|
|
1880
|
+
if (values.dynamic) {
|
|
1881
|
+
console.error("Generating dynamic schema loading code...");
|
|
1882
|
+
const dynamicCode = generateDynamicLoadingCode(inputFile, arrayBuffer.buffer);
|
|
1883
|
+
if (values.output) {
|
|
1884
|
+
writeFileSync(values.output, dynamicCode);
|
|
1885
|
+
console.error(`Dynamic loading code written to ${values.output}`);
|
|
1886
|
+
} else console.log(dynamicCode);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
console.error("Generating TypeScript code...");
|
|
1890
|
+
const files = generateFromRequest(CodeGeneratorRequestReader.fromBuffer(arrayBuffer.buffer), { runtimeImportPath: values.runtimePath || "@naeemo/capnp" });
|
|
1891
|
+
if (values.outDir) {
|
|
1892
|
+
mkdirSync(values.outDir, { recursive: true });
|
|
1893
|
+
for (const [filename, code] of files) {
|
|
1894
|
+
const outPath = join(values.outDir, filename);
|
|
1895
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
1896
|
+
writeFileSync(outPath, code);
|
|
1897
|
+
console.error(`Generated: ${outPath}`);
|
|
1898
|
+
}
|
|
1899
|
+
} else if (values.output) {
|
|
1900
|
+
const firstFile = files.values().next().value;
|
|
1901
|
+
if (firstFile) {
|
|
1902
|
+
writeFileSync(values.output, firstFile);
|
|
1903
|
+
console.error(`Output written to ${values.output}`);
|
|
1904
|
+
} else {
|
|
1905
|
+
console.error("Error: No files generated");
|
|
1906
|
+
process.exit(1);
|
|
1907
|
+
}
|
|
1908
|
+
} else {
|
|
1909
|
+
const firstFile = files.values().next().value;
|
|
1910
|
+
if (firstFile) console.log(firstFile);
|
|
1911
|
+
else {
|
|
1912
|
+
console.error("Error: No files generated");
|
|
1913
|
+
process.exit(1);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
} catch (err) {
|
|
1917
|
+
console.error("Error:", err.message);
|
|
1918
|
+
if (err.stderr) console.error(err.stderr.toString());
|
|
1919
|
+
process.exit(1);
|
|
1920
|
+
} finally {
|
|
1921
|
+
rmSync(tmpDir, {
|
|
1922
|
+
recursive: true,
|
|
1923
|
+
force: true
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
main();
|
|
1928
|
+
/**
|
|
1929
|
+
* Generate dynamic schema loading code for the given schema file
|
|
1930
|
+
*/
|
|
1931
|
+
function generateDynamicLoadingCode(inputFile, buffer) {
|
|
1932
|
+
const nodes = CodeGeneratorRequestReader.fromBuffer(buffer).nodes;
|
|
1933
|
+
const runtimePath = values.runtimePath || "@naeemo/capnp";
|
|
1934
|
+
const schemaName = basename(inputFile, ".capnp");
|
|
1935
|
+
const structTypes = [];
|
|
1936
|
+
const interfaceTypes = [];
|
|
1937
|
+
for (const node of nodes) {
|
|
1938
|
+
const id = node.id;
|
|
1939
|
+
const displayName = node.displayName;
|
|
1940
|
+
const shortName = displayName.split(".").pop() || displayName;
|
|
1941
|
+
if (node.isStruct) structTypes.push({
|
|
1942
|
+
id: `0x${id.toString(16)}n`,
|
|
1943
|
+
name: shortName,
|
|
1944
|
+
displayName
|
|
1945
|
+
});
|
|
1946
|
+
else if (node.isInterface) interfaceTypes.push({
|
|
1947
|
+
id: `0x${id.toString(16)}n`,
|
|
1948
|
+
name: shortName,
|
|
1949
|
+
displayName
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
return `/**
|
|
1953
|
+
* Dynamic Schema Loading for ${schemaName}.capnp
|
|
1954
|
+
*
|
|
1955
|
+
* This file provides runtime schema loading capabilities for ${schemaName}.capnp
|
|
1956
|
+
* Generated by capnp-ts-codegen --dynamic
|
|
1957
|
+
*
|
|
1958
|
+
* Usage:
|
|
1959
|
+
* import { load${toPascalCase(schemaName)}Schema, ${structTypes.map((t) => t.name).join(", ")} } from './${schemaName}-dynamic';
|
|
1960
|
+
*
|
|
1961
|
+
* // Load schema from remote connection
|
|
1962
|
+
* const schema = await load${toPascalCase(schemaName)}Schema(connection);
|
|
1963
|
+
*
|
|
1964
|
+
* // Use dynamic reader
|
|
1965
|
+
* const reader = createDynamicReader(schema.Person, buffer);
|
|
1966
|
+
* console.log(reader.get('name'));
|
|
1967
|
+
*/
|
|
1968
|
+
|
|
1969
|
+
import {
|
|
1970
|
+
RpcConnection,
|
|
1971
|
+
createDynamicReader,
|
|
1972
|
+
createDynamicWriter,
|
|
1973
|
+
dumpDynamicReader,
|
|
1974
|
+
type SchemaNode,
|
|
1975
|
+
type SchemaRegistry,
|
|
1976
|
+
} from '${runtimePath}';
|
|
1977
|
+
|
|
1978
|
+
// =============================================================================
|
|
1979
|
+
// Type IDs
|
|
1980
|
+
// =============================================================================
|
|
1981
|
+
|
|
1982
|
+
${structTypes.map((t) => `/** Type ID for ${t.displayName} */\nexport const ${t.name}TypeId = ${t.id};`).join("\n")}
|
|
1983
|
+
|
|
1984
|
+
${interfaceTypes.map((t) => `/** Interface ID for ${t.displayName} */\nexport const ${t.name}InterfaceId = ${t.id};`).join("\n")}
|
|
1985
|
+
|
|
1986
|
+
// =============================================================================
|
|
1987
|
+
// Schema Cache
|
|
1988
|
+
// =============================================================================
|
|
1989
|
+
|
|
1990
|
+
let cachedSchemaRegistry: SchemaRegistry | null = null;
|
|
1991
|
+
|
|
1992
|
+
// =============================================================================
|
|
1993
|
+
// Schema Loading Functions
|
|
1994
|
+
// =============================================================================
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Load all schemas for ${schemaName}.capnp from a remote connection.
|
|
1998
|
+
* This fetches schema information dynamically and caches it for reuse.
|
|
1999
|
+
*
|
|
2000
|
+
* @param connection - The RPC connection to fetch schemas from
|
|
2001
|
+
* @returns A registry containing all loaded schemas
|
|
2002
|
+
*/
|
|
2003
|
+
export async function load${toPascalCase(schemaName)}Schema(connection: RpcConnection): Promise<SchemaRegistry> {
|
|
2004
|
+
if (cachedSchemaRegistry) {
|
|
2005
|
+
return cachedSchemaRegistry;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Fetch all schemas
|
|
2009
|
+
const typeIds = [
|
|
2010
|
+
${structTypes.map((t) => `${t.name}TypeId`).join(",\n ")}
|
|
2011
|
+
];
|
|
2012
|
+
|
|
2013
|
+
for (const typeId of typeIds) {
|
|
2014
|
+
try {
|
|
2015
|
+
await connection.getDynamicSchema(typeId);
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
console.warn(\`Failed to load schema for type \${typeId}:\`, err);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
cachedSchemaRegistry = connection.getSchemaRegistry();
|
|
2022
|
+
return cachedSchemaRegistry;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* Get a specific schema node by type ID.
|
|
2027
|
+
* Loads from remote if not already cached.
|
|
2028
|
+
*
|
|
2029
|
+
* @param connection - The RPC connection
|
|
2030
|
+
* @param typeId - The type ID to fetch
|
|
2031
|
+
* @returns The schema node
|
|
2032
|
+
*/
|
|
2033
|
+
export async function getSchema(connection: RpcConnection, typeId: bigint): Promise<SchemaNode> {
|
|
2034
|
+
return connection.getDynamicSchema(typeId);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
/**
|
|
2038
|
+
* Clear the schema cache.
|
|
2039
|
+
* Call this if you need to re-fetch schemas from the remote server.
|
|
2040
|
+
*/
|
|
2041
|
+
export function clearSchemaCache(): void {
|
|
2042
|
+
cachedSchemaRegistry = null;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// =============================================================================
|
|
2046
|
+
// Dynamic Reader Helpers
|
|
2047
|
+
// =============================================================================
|
|
2048
|
+
|
|
2049
|
+
${structTypes.map((t) => `
|
|
2050
|
+
/**
|
|
2051
|
+
* Create a dynamic reader for ${t.name}
|
|
2052
|
+
*
|
|
2053
|
+
* @param buffer - The Cap'n Proto message buffer
|
|
2054
|
+
* @param registry - Optional schema registry (will use cached if available)
|
|
2055
|
+
* @returns A DynamicReader for the message
|
|
2056
|
+
*/
|
|
2057
|
+
export function create${toPascalCase(t.name)}Reader(
|
|
2058
|
+
buffer: ArrayBuffer | Uint8Array,
|
|
2059
|
+
registry?: SchemaRegistry
|
|
2060
|
+
): DynamicReader {
|
|
2061
|
+
const reg = registry || cachedSchemaRegistry;
|
|
2062
|
+
if (!reg) {
|
|
2063
|
+
throw new Error('Schema registry not loaded. Call load${toPascalCase(schemaName)}Schema first.');
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
const schema = reg.getNode(${t.name}TypeId);
|
|
2067
|
+
if (!schema) {
|
|
2068
|
+
throw new Error('Schema for ${t.name} not found in registry');
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
return createDynamicReader(schema, buffer);
|
|
2072
|
+
}
|
|
2073
|
+
`).join("\n")}
|
|
2074
|
+
|
|
2075
|
+
// =============================================================================
|
|
2076
|
+
// Utility Functions
|
|
2077
|
+
// =============================================================================
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* Dump all fields from a dynamic reader for debugging
|
|
2081
|
+
*/
|
|
2082
|
+
export { dumpDynamicReader };
|
|
2083
|
+
|
|
2084
|
+
/**
|
|
2085
|
+
* List all available types in this schema
|
|
2086
|
+
*/
|
|
2087
|
+
export function listTypes(): Array<{ name: string; typeId: string; kind: 'struct' | 'interface' }> {
|
|
2088
|
+
return [
|
|
2089
|
+
${structTypes.map((t) => `{ name: '${t.name}', typeId: '${t.id}', kind: 'struct' as const }`).join(",\n ")},
|
|
2090
|
+
${interfaceTypes.map((t) => `{ name: '${t.name}', typeId: '${t.id}', kind: 'interface' as const }`).join(",\n ")},
|
|
2091
|
+
];
|
|
2092
|
+
}
|
|
2093
|
+
`;
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Run interactive schema query tool
|
|
2097
|
+
*/
|
|
2098
|
+
async function runInteractiveMode(inputFile) {
|
|
2099
|
+
if (!checkCapnpTool()) {
|
|
2100
|
+
console.error("Error: capnp tool not found. Please install Cap'n Proto.");
|
|
2101
|
+
process.exit(1);
|
|
2102
|
+
}
|
|
2103
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "capnp-ts-"));
|
|
2104
|
+
const binFile = join(tmpDir, "schema.bin");
|
|
2105
|
+
try {
|
|
2106
|
+
console.log(`\n🔍 Loading schema: ${inputFile}...\n`);
|
|
2107
|
+
const inputDir = dirname(inputFile);
|
|
2108
|
+
execSync(`capnp compile -o- "${inputFile}" > "${binFile}"`, {
|
|
2109
|
+
cwd: inputDir,
|
|
2110
|
+
stdio: [
|
|
2111
|
+
"ignore",
|
|
2112
|
+
"ignore",
|
|
2113
|
+
"pipe"
|
|
2114
|
+
]
|
|
2115
|
+
});
|
|
2116
|
+
const buffer = readFileSync(binFile);
|
|
2117
|
+
const arrayBuffer = new Uint8Array(buffer.byteLength);
|
|
2118
|
+
arrayBuffer.set(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength));
|
|
2119
|
+
const nodes = CodeGeneratorRequestReader.fromBuffer(arrayBuffer.buffer).nodes;
|
|
2120
|
+
const typeIndex = /* @__PURE__ */ new Map();
|
|
2121
|
+
for (const node of nodes) {
|
|
2122
|
+
const id = node.id;
|
|
2123
|
+
const displayName = node.displayName;
|
|
2124
|
+
const shortName = displayName.split(".").pop() || displayName;
|
|
2125
|
+
let kind = "unknown";
|
|
2126
|
+
if (node.isStruct) kind = "struct";
|
|
2127
|
+
else if (node.isInterface) kind = "interface";
|
|
2128
|
+
else if (node.isEnum) kind = "enum";
|
|
2129
|
+
else if (node.isConst) kind = "const";
|
|
2130
|
+
typeIndex.set(shortName, {
|
|
2131
|
+
id,
|
|
2132
|
+
displayName,
|
|
2133
|
+
kind,
|
|
2134
|
+
node
|
|
2135
|
+
});
|
|
2136
|
+
typeIndex.set(displayName, {
|
|
2137
|
+
id,
|
|
2138
|
+
displayName,
|
|
2139
|
+
kind,
|
|
2140
|
+
node
|
|
2141
|
+
});
|
|
2142
|
+
typeIndex.set(id.toString(16), {
|
|
2143
|
+
id,
|
|
2144
|
+
displayName,
|
|
2145
|
+
kind,
|
|
2146
|
+
node
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
console.log("📋 Available types:");
|
|
2150
|
+
console.log("─".repeat(60));
|
|
2151
|
+
const structs = [];
|
|
2152
|
+
const interfaces = [];
|
|
2153
|
+
const enums = [];
|
|
2154
|
+
for (const [name, info] of typeIndex) {
|
|
2155
|
+
if (name.includes(".")) continue;
|
|
2156
|
+
if (info.kind === "struct") structs.push(name);
|
|
2157
|
+
else if (info.kind === "interface") interfaces.push(name);
|
|
2158
|
+
else if (info.kind === "enum") enums.push(name);
|
|
2159
|
+
}
|
|
2160
|
+
if (structs.length > 0) {
|
|
2161
|
+
console.log("\n🏗️ Structs:");
|
|
2162
|
+
for (const name of structs.sort()) {
|
|
2163
|
+
const info = typeIndex.get(name);
|
|
2164
|
+
console.log(` ${name.padEnd(30)} (0x${info.id.toString(16)})`);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
if (interfaces.length > 0) {
|
|
2168
|
+
console.log("\n🔌 Interfaces:");
|
|
2169
|
+
for (const name of interfaces.sort()) {
|
|
2170
|
+
const info = typeIndex.get(name);
|
|
2171
|
+
console.log(` ${name.padEnd(30)} (0x${info.id.toString(16)})`);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (enums.length > 0) {
|
|
2175
|
+
console.log("\n📊 Enums:");
|
|
2176
|
+
for (const name of enums.sort()) {
|
|
2177
|
+
const info = typeIndex.get(name);
|
|
2178
|
+
console.log(` ${name.padEnd(30)} (0x${info.id.toString(16)})`);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
console.log(`\n${"─".repeat(60)}`);
|
|
2182
|
+
console.log("💡 Commands:");
|
|
2183
|
+
console.log(" inspect <type> - Show detailed information about a type");
|
|
2184
|
+
console.log(" ids - List all type IDs");
|
|
2185
|
+
console.log(" export - Export schema info as JSON");
|
|
2186
|
+
console.log(" quit - Exit interactive mode");
|
|
2187
|
+
console.log("");
|
|
2188
|
+
const stdin = process.stdin;
|
|
2189
|
+
const stdout = process.stdout;
|
|
2190
|
+
stdin.setEncoding("utf8");
|
|
2191
|
+
stdin.resume();
|
|
2192
|
+
stdout.write("> ");
|
|
2193
|
+
stdin.on("data", (data) => {
|
|
2194
|
+
const line = data.trim();
|
|
2195
|
+
const parts = line.split(/\s+/);
|
|
2196
|
+
const command = parts[0];
|
|
2197
|
+
const arg = parts[1];
|
|
2198
|
+
switch (command) {
|
|
2199
|
+
case "quit":
|
|
2200
|
+
case "exit":
|
|
2201
|
+
case "q":
|
|
2202
|
+
console.log("👋 Goodbye!");
|
|
2203
|
+
process.exit(0);
|
|
2204
|
+
break;
|
|
2205
|
+
case "inspect":
|
|
2206
|
+
if (!arg) console.log("❌ Usage: inspect <type-name>");
|
|
2207
|
+
else {
|
|
2208
|
+
const info = typeIndex.get(arg);
|
|
2209
|
+
if (info) {
|
|
2210
|
+
console.log(`\n🔍 ${info.displayName}`);
|
|
2211
|
+
console.log(` ${"─".repeat(50)}`);
|
|
2212
|
+
console.log(` Type ID: 0x${info.id.toString(16)}`);
|
|
2213
|
+
console.log(` Kind: ${info.kind}`);
|
|
2214
|
+
console.log("");
|
|
2215
|
+
} else console.log(`❌ Type "${arg}" not found`);
|
|
2216
|
+
}
|
|
2217
|
+
break;
|
|
2218
|
+
case "ids":
|
|
2219
|
+
console.log("\n📋 All Type IDs:");
|
|
2220
|
+
console.log(` ${"─".repeat(50)}`);
|
|
2221
|
+
for (const [name, info] of typeIndex) {
|
|
2222
|
+
if (name.includes(".")) continue;
|
|
2223
|
+
console.log(` 0x${info.id.toString(16).padStart(16, "0")} ${info.displayName}`);
|
|
2224
|
+
}
|
|
2225
|
+
console.log("");
|
|
2226
|
+
break;
|
|
2227
|
+
case "export": {
|
|
2228
|
+
const exportData = {
|
|
2229
|
+
sourceFile: inputFile,
|
|
2230
|
+
types: Array.from(typeIndex.entries()).filter(([name]) => !name.includes(".")).map(([name, info]) => ({
|
|
2231
|
+
name,
|
|
2232
|
+
displayName: info.displayName,
|
|
2233
|
+
typeId: `0x${info.id.toString(16)}`,
|
|
2234
|
+
kind: info.kind
|
|
2235
|
+
}))
|
|
2236
|
+
};
|
|
2237
|
+
console.log(JSON.stringify(exportData, null, 2));
|
|
2238
|
+
break;
|
|
2239
|
+
}
|
|
2240
|
+
case "help":
|
|
2241
|
+
case "h":
|
|
2242
|
+
console.log("\n💡 Commands:");
|
|
2243
|
+
console.log(" inspect <type> - Show detailed information about a type");
|
|
2244
|
+
console.log(" ids - List all type IDs");
|
|
2245
|
+
console.log(" export - Export schema info as JSON");
|
|
2246
|
+
console.log(" quit - Exit interactive mode");
|
|
2247
|
+
console.log("");
|
|
2248
|
+
break;
|
|
2249
|
+
default: if (line) {
|
|
2250
|
+
console.log(`❌ Unknown command: ${command}`);
|
|
2251
|
+
console.log(" Type \"help\" for available commands");
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
stdout.write("> ");
|
|
2255
|
+
});
|
|
2256
|
+
await new Promise(() => {});
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
console.error("Error:", err.message);
|
|
2259
|
+
process.exit(1);
|
|
2260
|
+
} finally {
|
|
2261
|
+
rmSync(tmpDir, {
|
|
2262
|
+
recursive: true,
|
|
2263
|
+
force: true
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Convert string to PascalCase
|
|
2269
|
+
*/
|
|
2270
|
+
function toPascalCase(str) {
|
|
2271
|
+
return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
2272
|
+
}
|
|
391
2273
|
|
|
392
2274
|
//#endregion
|
|
393
2275
|
export { };
|