hm-pt-core 1.0.0 → 1.0.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/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/trace/decode_raw.js +5 -2
- package/dist/trace/parse.d.ts +12 -1
- package/dist/trace/parse.js +275 -54
- package/dist/trace/printf.d.ts +15 -2
- package/dist/trace/printf.js +237 -41
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export type { RecordSampleHeader, CallchainEntry, RawEntry, CallchainFramesEntry
|
|
|
5
5
|
export { isPerfDataLike } from "./perf/is_perf_data_like.js";
|
|
6
6
|
export type { HitraceRecordSnapshot, HitraceThreadIndexSnapshot, } from "./hitrace/contract.js";
|
|
7
7
|
export type { TraceFormatField, ParsedTraceFormat, FieldDictTransformFn, TraceParserRegistry, DecodedRawSample, TracePrintMode, DecodeRawByRegistryOptions, } from "./trace/types.js";
|
|
8
|
-
export type { Endian } from "./trace/parse.js";
|
|
8
|
+
export type { Endian, BuildTraceParserRegistryOptions } from "./trace/parse.js";
|
|
9
|
+
export { TraceFieldDecodeError } from "./trace/parse.js";
|
|
9
10
|
export type { PrintfSpec } from "./trace/printf.js";
|
|
10
11
|
export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, bufferToRawHexLines, buildTraceParserRegistry, } from "./trace/parse.js";
|
|
11
12
|
export { formatPrintfValue, tokenizePrintfFormat } from "./trace/printf.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* hm-pt-core:perf trace 公共类型与 trace format 解码原语。
|
|
3
3
|
*/
|
|
4
4
|
export { isPerfDataLike } from "./perf/is_perf_data_like.js";
|
|
5
|
+
export { TraceFieldDecodeError } from "./trace/parse.js";
|
|
5
6
|
export { parseTraceFormat, parseCommonFieldsFromRaw, parseAllFieldsFromRaw, rawHexLinesToBuffer, bufferToRawHexLines, buildTraceParserRegistry, } from "./trace/parse.js";
|
|
6
7
|
export { formatPrintfValue, tokenizePrintfFormat } from "./trace/printf.js";
|
|
7
8
|
export { decodeRawByRegistry } from "./trace/decode_raw.js";
|
package/dist/trace/decode_raw.js
CHANGED
|
@@ -21,9 +21,10 @@ export function decodeRawByRegistry(raw, registry, options = {}) {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
const allFields = parseAllFieldsFromRaw(raw, fmt);
|
|
24
|
+
const renderContext = { eventName: fmt.eventName, eventId };
|
|
24
25
|
const mode = options.tracePrintMode ?? "printf";
|
|
25
26
|
if (fmt.printFmt && mode === "fieldDict") {
|
|
26
|
-
let renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields);
|
|
27
|
+
let renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields, renderContext);
|
|
27
28
|
const transform = registry.transformFieldDictByEventId?.get(eventId);
|
|
28
29
|
if (transform) {
|
|
29
30
|
renderedFieldDict = transform({
|
|
@@ -42,7 +43,9 @@ export function decodeRawByRegistry(raw, registry, options = {}) {
|
|
|
42
43
|
skipped: false,
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
const renderedText = fmt.printFmt
|
|
46
|
+
const renderedText = fmt.printFmt
|
|
47
|
+
? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields, renderContext)
|
|
48
|
+
: undefined;
|
|
46
49
|
return {
|
|
47
50
|
commonType: eventId,
|
|
48
51
|
commonFields,
|
package/dist/trace/parse.d.ts
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { FieldDictTransformFn, ParsedTraceFormat, TraceParserRegistry } from "./types.js";
|
|
6
6
|
export type Endian = "le" | "be";
|
|
7
|
+
export declare class TraceFieldDecodeError extends Error {
|
|
8
|
+
readonly fieldName: string;
|
|
9
|
+
readonly offset: number;
|
|
10
|
+
readonly size: number;
|
|
11
|
+
readonly rawLength: number;
|
|
12
|
+
constructor(message: string, fieldName: string, offset: number, size: number, rawLength: number);
|
|
13
|
+
}
|
|
14
|
+
export interface BuildTraceParserRegistryOptions {
|
|
15
|
+
/** 允许后注册的 format 覆盖同 ID 事件;默认 false,重复 ID 会抛错 */
|
|
16
|
+
allowDuplicateEventIds?: boolean;
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
19
|
* 解析 sample/trace_format 风格的文本,得到字段列表
|
|
9
20
|
*/
|
|
@@ -45,4 +56,4 @@ export declare function bufferToRawHexLines(raw: Uint8Array, bytesPerLine?: numb
|
|
|
45
56
|
export declare function buildTraceParserRegistry(formatTexts: Array<string | {
|
|
46
57
|
text: string;
|
|
47
58
|
transformFieldDict?: FieldDictTransformFn;
|
|
48
|
-
}
|
|
59
|
+
}>, options?: BuildTraceParserRegistryOptions): TraceParserRegistry;
|
package/dist/trace/parse.js
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
* @module traceFormat
|
|
4
4
|
*/
|
|
5
5
|
const UTF8_DECODER = new TextDecoder("utf-8", { fatal: false });
|
|
6
|
+
export class TraceFieldDecodeError extends Error {
|
|
7
|
+
fieldName;
|
|
8
|
+
offset;
|
|
9
|
+
size;
|
|
10
|
+
rawLength;
|
|
11
|
+
constructor(message, fieldName, offset, size, rawLength) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.fieldName = fieldName;
|
|
14
|
+
this.offset = offset;
|
|
15
|
+
this.size = size;
|
|
16
|
+
this.rawLength = rawLength;
|
|
17
|
+
this.name = "TraceFieldDecodeError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
6
20
|
const FIELD_LINE_RE = /^\s*field\s*:\s*([^;]+)\s*;\s*offset\s*:\s*(\d+)\s*;\s*size\s*:\s*(\d+)\s*;\s*signed\s*:\s*([01])\s*;?\s*$/i;
|
|
7
21
|
function hexNibble(ch) {
|
|
8
22
|
const code = ch.charCodeAt(0);
|
|
@@ -16,10 +30,129 @@ function hexNibble(ch) {
|
|
|
16
30
|
function hexPairToByte(hi, lo) {
|
|
17
31
|
const h = hexNibble(hi);
|
|
18
32
|
const l = hexNibble(lo);
|
|
19
|
-
if (h < 0 || l < 0)
|
|
20
|
-
|
|
33
|
+
if (h < 0 || l < 0) {
|
|
34
|
+
throw new Error(`Invalid hex byte: "${hi}${lo}"`);
|
|
35
|
+
}
|
|
21
36
|
return (h << 4) | l;
|
|
22
37
|
}
|
|
38
|
+
function readCharArrayAsString(raw, offset, size, fieldName) {
|
|
39
|
+
if (offset < 0 || offset + size > raw.length) {
|
|
40
|
+
throw new TraceFieldDecodeError(`Field "${fieldName}" out of bounds: offset=${offset}, size=${size}, raw.length=${raw.length}`, fieldName, offset, size, raw.length);
|
|
41
|
+
}
|
|
42
|
+
const bytes = raw.subarray(offset, offset + size);
|
|
43
|
+
const nul = bytes.indexOf(0);
|
|
44
|
+
const slice = nul >= 0 ? bytes.subarray(0, nul) : bytes;
|
|
45
|
+
return UTF8_DECODER.decode(slice);
|
|
46
|
+
}
|
|
47
|
+
function decodePrintFmtEscape(line, escapeStart) {
|
|
48
|
+
let i = escapeStart + 1;
|
|
49
|
+
if (i >= line.length) {
|
|
50
|
+
throw new Error(`Incomplete escape in print fmt string literal at index ${escapeStart}`);
|
|
51
|
+
}
|
|
52
|
+
const esc = line[i];
|
|
53
|
+
i++;
|
|
54
|
+
switch (esc) {
|
|
55
|
+
case '"':
|
|
56
|
+
return { value: '"', next: i };
|
|
57
|
+
case "\\":
|
|
58
|
+
return { value: "\\", next: i };
|
|
59
|
+
case "n":
|
|
60
|
+
return { value: "\n", next: i };
|
|
61
|
+
case "t":
|
|
62
|
+
return { value: "\t", next: i };
|
|
63
|
+
case "r":
|
|
64
|
+
return { value: "\r", next: i };
|
|
65
|
+
case "x": {
|
|
66
|
+
if (i + 1 >= line.length) {
|
|
67
|
+
throw new Error(`Incomplete \\x escape in print fmt string literal at index ${escapeStart}`);
|
|
68
|
+
}
|
|
69
|
+
const hi = hexNibble(line[i]);
|
|
70
|
+
const lo = hexNibble(line[i + 1]);
|
|
71
|
+
if (hi < 0 || lo < 0) {
|
|
72
|
+
throw new Error(`Invalid \\x escape in print fmt string literal at index ${escapeStart}`);
|
|
73
|
+
}
|
|
74
|
+
return { value: String.fromCharCode((hi << 4) | lo), next: i + 2 };
|
|
75
|
+
}
|
|
76
|
+
default: {
|
|
77
|
+
if (esc >= "0" && esc <= "7") {
|
|
78
|
+
let oct = esc.charCodeAt(0) - 48;
|
|
79
|
+
let digits = 1;
|
|
80
|
+
while (digits < 3 && i < line.length && line[i] >= "0" && line[i] <= "7") {
|
|
81
|
+
oct = oct * 8 + (line[i].charCodeAt(0) - 48);
|
|
82
|
+
i++;
|
|
83
|
+
digits++;
|
|
84
|
+
}
|
|
85
|
+
return { value: String.fromCharCode(oct & 0xff), next: i };
|
|
86
|
+
}
|
|
87
|
+
return { value: "\\" + esc, next: i };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** 扫描 print fmt 行中的双引号字符串字面量,支持常见 C 字符串转义 */
|
|
92
|
+
function parsePrintFmtStringLiteral(line, start) {
|
|
93
|
+
if (line[start] !== '"')
|
|
94
|
+
return undefined;
|
|
95
|
+
let i = start + 1;
|
|
96
|
+
let value = "";
|
|
97
|
+
while (i < line.length) {
|
|
98
|
+
const ch = line[i];
|
|
99
|
+
if (ch === "\\") {
|
|
100
|
+
const decoded = decodePrintFmtEscape(line, i);
|
|
101
|
+
value += decoded.value;
|
|
102
|
+
i = decoded.next;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (ch === '"') {
|
|
106
|
+
return { value, end: i + 1 };
|
|
107
|
+
}
|
|
108
|
+
value += ch;
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
function scanBalancedPrintArgs(argsPart) {
|
|
114
|
+
const args = [];
|
|
115
|
+
let i = 0;
|
|
116
|
+
while (i < argsPart.length) {
|
|
117
|
+
while (i < argsPart.length && /\s/.test(argsPart[i]))
|
|
118
|
+
i++;
|
|
119
|
+
if (i >= argsPart.length)
|
|
120
|
+
break;
|
|
121
|
+
const start = i;
|
|
122
|
+
let depth = 0;
|
|
123
|
+
while (i < argsPart.length) {
|
|
124
|
+
const ch = argsPart[i];
|
|
125
|
+
if (ch === "(")
|
|
126
|
+
depth++;
|
|
127
|
+
else if (ch === ")")
|
|
128
|
+
depth = Math.max(0, depth - 1);
|
|
129
|
+
else if (ch === "," && depth === 0)
|
|
130
|
+
break;
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
const expr = argsPart.slice(start, i).trim();
|
|
134
|
+
if (expr)
|
|
135
|
+
args.push(expr);
|
|
136
|
+
if (argsPart[i] === ",")
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
return args;
|
|
140
|
+
}
|
|
141
|
+
function parsePrintFmtLine(line) {
|
|
142
|
+
const prefix = line.match(/^print\s+fmt\s*:\s*/i);
|
|
143
|
+
if (!prefix)
|
|
144
|
+
return undefined;
|
|
145
|
+
const lit = parsePrintFmtStringLiteral(line, prefix[0].length);
|
|
146
|
+
if (!lit)
|
|
147
|
+
return undefined;
|
|
148
|
+
const rest = line.slice(lit.end).trim();
|
|
149
|
+
if (!rest)
|
|
150
|
+
return { printFmt: lit.value, printArgs: [] };
|
|
151
|
+
const argsMatch = rest.match(/^,\s*(.*)$/);
|
|
152
|
+
if (!argsMatch)
|
|
153
|
+
return undefined;
|
|
154
|
+
return { printFmt: lit.value, printArgs: scanBalancedPrintArgs(argsMatch[1]) };
|
|
155
|
+
}
|
|
23
156
|
function splitTypeAndName(rest) {
|
|
24
157
|
const trimmed = rest.trim();
|
|
25
158
|
const m = trimmed.match(/^(.+?)\s+(\w+(?:\[[^\]]*])?)$/);
|
|
@@ -61,17 +194,10 @@ export function parseTraceFormat(text) {
|
|
|
61
194
|
continue;
|
|
62
195
|
}
|
|
63
196
|
if (/^print\s+fmt\s*:/i.test(t)) {
|
|
64
|
-
const pf = t
|
|
197
|
+
const pf = parsePrintFmtLine(t);
|
|
65
198
|
if (pf) {
|
|
66
|
-
printFmt = pf
|
|
67
|
-
|
|
68
|
-
if (argsPart) {
|
|
69
|
-
const exprs = argsPart.match(/REC->[^,]+/g);
|
|
70
|
-
printArgs = exprs ? exprs.map((s) => s.trim()) : [];
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
printArgs = [];
|
|
74
|
-
}
|
|
199
|
+
printFmt = pf.printFmt;
|
|
200
|
+
printArgs = pf.printArgs;
|
|
75
201
|
}
|
|
76
202
|
break;
|
|
77
203
|
}
|
|
@@ -92,13 +218,93 @@ export function parseTraceFormat(text) {
|
|
|
92
218
|
function normalizeType(t) {
|
|
93
219
|
return t.replace(/\s+/g, " ").trim().toLowerCase();
|
|
94
220
|
}
|
|
221
|
+
function isCharType(typeName) {
|
|
222
|
+
const t = normalizeType(typeName);
|
|
223
|
+
return t === "char" || t === "signed char" || t === "unsigned char";
|
|
224
|
+
}
|
|
225
|
+
function isDataLocType(typeName) {
|
|
226
|
+
return normalizeType(typeName).includes("__data_loc");
|
|
227
|
+
}
|
|
228
|
+
function parseDataLocElementType(typeName, signed) {
|
|
229
|
+
const t = normalizeType(typeName);
|
|
230
|
+
const m = t.match(/^__data_loc\s+(.+)\[\]$/);
|
|
231
|
+
if (!m)
|
|
232
|
+
return undefined;
|
|
233
|
+
const elemTypeName = m[1].trim();
|
|
234
|
+
const elemSize = inferScalarSize(elemTypeName, signed);
|
|
235
|
+
if (elemSize === undefined)
|
|
236
|
+
return undefined;
|
|
237
|
+
return { elemTypeName, elemSize, signed: isSignedElementType(elemTypeName, signed) };
|
|
238
|
+
}
|
|
239
|
+
function isSignedElementType(elemTypeName, fieldSigned) {
|
|
240
|
+
const t = normalizeType(elemTypeName);
|
|
241
|
+
if (t.includes("unsigned"))
|
|
242
|
+
return false;
|
|
243
|
+
if (t === "char" && !fieldSigned)
|
|
244
|
+
return false;
|
|
245
|
+
return fieldSigned || t === "signed char" || t === "short" || t === "int" || t === "long" || t === "long long" || t === "__s64";
|
|
246
|
+
}
|
|
247
|
+
function inferScalarSize(typeName, signed) {
|
|
248
|
+
const t = normalizeType(typeName);
|
|
249
|
+
if (t.includes("double"))
|
|
250
|
+
return 8;
|
|
251
|
+
if (t.includes("long long") || t === "__s64")
|
|
252
|
+
return 8;
|
|
253
|
+
if (t.includes("long"))
|
|
254
|
+
return 8;
|
|
255
|
+
if (t.includes("short"))
|
|
256
|
+
return 2;
|
|
257
|
+
if (t.includes("char") || t.includes("int") || t.includes("float"))
|
|
258
|
+
return 4;
|
|
259
|
+
if (t.includes("unsigned") || signed)
|
|
260
|
+
return 4;
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
function assertFieldInBounds(fieldName, offset, size, rawLength) {
|
|
264
|
+
if (offset < 0 || offset + size > rawLength) {
|
|
265
|
+
throw new TraceFieldDecodeError(`Field "${fieldName}" out of bounds: offset=${offset}, size=${size}, raw.length=${rawLength}`, fieldName, offset, size, rawLength);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function readLocatorField(view, field) {
|
|
269
|
+
assertFieldInBounds(field.name, field.offset, field.size, view.byteLength);
|
|
270
|
+
const locField = {
|
|
271
|
+
...field,
|
|
272
|
+
typeName: "unsigned int",
|
|
273
|
+
signed: false,
|
|
274
|
+
size: 4,
|
|
275
|
+
};
|
|
276
|
+
const loc = readFieldScalarUnchecked(view, locField);
|
|
277
|
+
if (typeof loc !== "number") {
|
|
278
|
+
throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc locator is invalid`, field.name, field.offset, field.size, view.byteLength);
|
|
279
|
+
}
|
|
280
|
+
return loc;
|
|
281
|
+
}
|
|
282
|
+
function readDynamicRegionAsArray(raw, view, field, dataOffset, byteLen, elemTypeName, elemSize, elemSigned) {
|
|
283
|
+
if (byteLen === 0)
|
|
284
|
+
return [];
|
|
285
|
+
if (byteLen % elemSize !== 0) {
|
|
286
|
+
throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc length ${byteLen} is not divisible by element size ${elemSize}`, field.name, dataOffset, byteLen, raw.length);
|
|
287
|
+
}
|
|
288
|
+
assertFieldInBounds(field.name, dataOffset, byteLen, raw.length);
|
|
289
|
+
const count = byteLen / elemSize;
|
|
290
|
+
const values = [];
|
|
291
|
+
for (let i = 0; i < count; i++) {
|
|
292
|
+
const elemField = {
|
|
293
|
+
typeName: elemTypeName,
|
|
294
|
+
name: field.name,
|
|
295
|
+
offset: dataOffset + i * elemSize,
|
|
296
|
+
size: elemSize,
|
|
297
|
+
signed: elemSigned,
|
|
298
|
+
};
|
|
299
|
+
values.push(readFieldScalarUnchecked(view, elemField));
|
|
300
|
+
}
|
|
301
|
+
return values;
|
|
302
|
+
}
|
|
95
303
|
/**
|
|
96
304
|
* 从 little-endian 原始缓冲区按字段描述读一个标量
|
|
97
305
|
*/
|
|
98
|
-
function
|
|
306
|
+
function readFieldScalarUnchecked(view, field) {
|
|
99
307
|
const { offset, size, signed, typeName } = field;
|
|
100
|
-
if (offset + size > view.byteLength)
|
|
101
|
-
return undefined;
|
|
102
308
|
const t = normalizeType(typeName);
|
|
103
309
|
if (size === 1) {
|
|
104
310
|
if (signed || t === "signed char") {
|
|
@@ -130,7 +336,14 @@ function readFieldScalar(view, field) {
|
|
|
130
336
|
}
|
|
131
337
|
return view.getBigUint64(offset, true);
|
|
132
338
|
}
|
|
133
|
-
|
|
339
|
+
throw new TraceFieldDecodeError(`Field "${field.name}" has unsupported scalar size ${size}`, field.name, offset, size, view.byteLength);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 从 little-endian 原始缓冲区按字段描述读一个标量;越界时抛出 TraceFieldDecodeError。
|
|
343
|
+
*/
|
|
344
|
+
function readFieldScalar(view, field) {
|
|
345
|
+
assertFieldInBounds(field.name, field.offset, field.size, view.byteLength);
|
|
346
|
+
return readFieldScalarUnchecked(view, field);
|
|
134
347
|
}
|
|
135
348
|
function parseArrayName(name) {
|
|
136
349
|
const m = name.match(/^(\w+)\[(\d+)\]$/);
|
|
@@ -144,9 +357,10 @@ function readFieldValue(view, field) {
|
|
|
144
357
|
return readFieldScalar(view, field);
|
|
145
358
|
if (arr.len <= 0)
|
|
146
359
|
return [];
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
360
|
+
if (field.size % arr.len !== 0) {
|
|
361
|
+
throw new TraceFieldDecodeError(`Field "${field.name}" size ${field.size} is not divisible by array length ${arr.len}`, field.name, field.offset, field.size, view.byteLength);
|
|
362
|
+
}
|
|
363
|
+
const elemSize = field.size / arr.len;
|
|
150
364
|
const values = [];
|
|
151
365
|
for (let i = 0; i < arr.len; i++) {
|
|
152
366
|
const elemField = {
|
|
@@ -155,10 +369,7 @@ function readFieldValue(view, field) {
|
|
|
155
369
|
offset: field.offset + i * elemSize,
|
|
156
370
|
size: elemSize,
|
|
157
371
|
};
|
|
158
|
-
|
|
159
|
-
if (v === undefined)
|
|
160
|
-
return undefined;
|
|
161
|
-
values.push(v);
|
|
372
|
+
values.push(readFieldScalar(view, elemField));
|
|
162
373
|
}
|
|
163
374
|
return values;
|
|
164
375
|
}
|
|
@@ -171,10 +382,9 @@ export function parseCommonFieldsFromRaw(raw, format) {
|
|
|
171
382
|
for (const field of format.fields) {
|
|
172
383
|
if (!field.name.startsWith("common_"))
|
|
173
384
|
continue;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
385
|
+
if (field.offset + field.size > view.byteLength)
|
|
386
|
+
continue;
|
|
387
|
+
out[field.name] = readFieldScalar(view, field);
|
|
178
388
|
}
|
|
179
389
|
return out;
|
|
180
390
|
}
|
|
@@ -188,23 +398,18 @@ export function parseAllFieldsFromRaw(raw, format) {
|
|
|
188
398
|
const arr = parseArrayName(field.name);
|
|
189
399
|
const key = arr ? arr.baseName : field.name;
|
|
190
400
|
const t = normalizeType(field.typeName);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const offset = loc & 0xffff;
|
|
204
|
-
const len = (loc >>> 16) & 0xffff;
|
|
205
|
-
if (offset >= 0 && len > 0 && offset + len <= raw.length) {
|
|
206
|
-
const bytes = raw.subarray(offset, offset + len);
|
|
207
|
-
// 去掉末尾 \0
|
|
401
|
+
if (isDataLocType(field.typeName)) {
|
|
402
|
+
const loc = readLocatorField(view, field);
|
|
403
|
+
const locKey = `__data_loc_${field.name}`;
|
|
404
|
+
out[locKey] = loc;
|
|
405
|
+
const dataOffset = loc & 0xffff;
|
|
406
|
+
const byteLen = (loc >>> 16) & 0xffff;
|
|
407
|
+
if (byteLen > 0 && (dataOffset < 0 || dataOffset + byteLen > raw.length)) {
|
|
408
|
+
throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc out of bounds: offset=${dataOffset}, len=${byteLen}, raw.length=${raw.length}`, field.name, dataOffset, byteLen, raw.length);
|
|
409
|
+
}
|
|
410
|
+
if (t.includes("char")) {
|
|
411
|
+
if (byteLen > 0) {
|
|
412
|
+
const bytes = raw.subarray(dataOffset, dataOffset + byteLen);
|
|
208
413
|
const nul = bytes.indexOf(0);
|
|
209
414
|
const slice = nul >= 0 ? bytes.subarray(0, nul) : bytes;
|
|
210
415
|
out[field.name] = UTF8_DECODER.decode(slice);
|
|
@@ -212,13 +417,20 @@ export function parseAllFieldsFromRaw(raw, format) {
|
|
|
212
417
|
else {
|
|
213
418
|
out[field.name] = "";
|
|
214
419
|
}
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const elemInfo = parseDataLocElementType(field.typeName, field.signed);
|
|
423
|
+
if (!elemInfo) {
|
|
424
|
+
throw new TraceFieldDecodeError(`Field "${field.name}" uses unsupported __data_loc element type "${field.typeName}"`, field.name, field.offset, field.size, raw.length);
|
|
215
425
|
}
|
|
426
|
+
out[key] = readDynamicRegionAsArray(raw, view, field, dataOffset, byteLen, elemInfo.elemTypeName, elemInfo.elemSize, elemInfo.signed);
|
|
216
427
|
continue;
|
|
217
428
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
429
|
+
if (arr && isCharType(field.typeName)) {
|
|
430
|
+
out[key] = readCharArrayAsString(raw, field.offset, field.size, field.name);
|
|
431
|
+
continue;
|
|
221
432
|
}
|
|
433
|
+
out[key] = readFieldValue(view, field);
|
|
222
434
|
}
|
|
223
435
|
return out;
|
|
224
436
|
}
|
|
@@ -233,8 +445,9 @@ export function rawHexLinesToBuffer(lines, endian = "le") {
|
|
|
233
445
|
let totalBytes = 0;
|
|
234
446
|
for (const { hex } of lines) {
|
|
235
447
|
let s = hex.replace(/^0x/i, "").trim();
|
|
236
|
-
if (!s)
|
|
237
|
-
|
|
448
|
+
if (!s) {
|
|
449
|
+
throw new Error(`Invalid hex token: "${hex}"`);
|
|
450
|
+
}
|
|
238
451
|
if (s.length % 2 === 1)
|
|
239
452
|
s = "0" + s;
|
|
240
453
|
normalizedTokens.push(s);
|
|
@@ -261,8 +474,9 @@ export function rawHexLinesToBuffer(lines, endian = "le") {
|
|
|
261
474
|
* 默认每行 4 字节(即 8 hex digits)。
|
|
262
475
|
*/
|
|
263
476
|
export function bufferToRawHexLines(raw, bytesPerLine = 4, endian = "le") {
|
|
264
|
-
if (bytesPerLine <= 0)
|
|
265
|
-
|
|
477
|
+
if (!Number.isSafeInteger(bytesPerLine) || bytesPerLine <= 0) {
|
|
478
|
+
throw new RangeError(`bytesPerLine must be a positive safe integer, got ${bytesPerLine}`);
|
|
479
|
+
}
|
|
266
480
|
const out = [];
|
|
267
481
|
for (let i = 0; i < raw.length; i += bytesPerLine) {
|
|
268
482
|
const end = Math.min(i + bytesPerLine, raw.length);
|
|
@@ -292,14 +506,18 @@ export function bufferToRawHexLines(raw, bytesPerLine = 4, endian = "le") {
|
|
|
292
506
|
*
|
|
293
507
|
* 若用户未配置 transformFieldDict,或未使用 fieldDict 模式,则不会做任何额外处理。
|
|
294
508
|
*/
|
|
295
|
-
export function buildTraceParserRegistry(formatTexts) {
|
|
509
|
+
export function buildTraceParserRegistry(formatTexts, options = {}) {
|
|
296
510
|
const byEventId = new Map();
|
|
297
511
|
let commonFormat;
|
|
298
512
|
const transformFieldDictByEventId = new Map();
|
|
299
|
-
|
|
513
|
+
formatTexts.forEach((src, sourceIndex) => {
|
|
300
514
|
const text = typeof src === "string" ? src : src.text;
|
|
301
515
|
const fmt = parseTraceFormat(text);
|
|
302
516
|
if (fmt.eventId !== undefined) {
|
|
517
|
+
const existing = byEventId.get(fmt.eventId);
|
|
518
|
+
if (existing && !options.allowDuplicateEventIds) {
|
|
519
|
+
throw new Error(`Duplicate event ID ${fmt.eventId}: "${existing.eventName ?? "unknown"}" conflicts with "${fmt.eventName ?? "unknown"}" at format index ${sourceIndex}`);
|
|
520
|
+
}
|
|
303
521
|
byEventId.set(fmt.eventId, fmt);
|
|
304
522
|
if (!commonFormat && fmt.fields.some((f) => f.name === "common_type")) {
|
|
305
523
|
commonFormat = fmt;
|
|
@@ -307,8 +525,11 @@ export function buildTraceParserRegistry(formatTexts) {
|
|
|
307
525
|
if (typeof src !== "string" && src.transformFieldDict) {
|
|
308
526
|
transformFieldDictByEventId.set(fmt.eventId, src.transformFieldDict);
|
|
309
527
|
}
|
|
528
|
+
else {
|
|
529
|
+
transformFieldDictByEventId.delete(fmt.eventId);
|
|
530
|
+
}
|
|
310
531
|
}
|
|
311
|
-
}
|
|
532
|
+
});
|
|
312
533
|
return {
|
|
313
534
|
byEventId,
|
|
314
535
|
commonFormat,
|
package/dist/trace/printf.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @module traceFormat
|
|
4
4
|
*/
|
|
5
5
|
type PrintFieldMap = Record<string, number | bigint | string | Array<number | bigint>>;
|
|
6
|
+
/** printf 格式串解析缓存上限(LRU 淘汰) */
|
|
7
|
+
export declare const PRINTF_CACHE_MAX_SIZE = 256;
|
|
6
8
|
/** 与 C printf 接近的转换说明符(不含 * 动态宽度等) */
|
|
7
9
|
export interface PrintfSpec {
|
|
8
10
|
flags: string;
|
|
@@ -27,6 +29,17 @@ export declare function tokenizePrintfFormat(printFmt: string): Array<{
|
|
|
27
29
|
kind: "spec";
|
|
28
30
|
spec: PrintfSpec;
|
|
29
31
|
}>;
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
+
export interface PrintFmtRenderContext {
|
|
33
|
+
eventName?: string;
|
|
34
|
+
eventId?: number;
|
|
35
|
+
}
|
|
36
|
+
/** 测试专用:重置 printf 解析缓存 */
|
|
37
|
+
export declare function resetPrintfCachesForTest(): void;
|
|
38
|
+
/** 测试专用:读取 printf 解析缓存条目数 */
|
|
39
|
+
export declare function getPrintfCacheSizesForTest(): {
|
|
40
|
+
tokens: number;
|
|
41
|
+
specs: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function renderPrintFmtAsFieldDict(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap, context?: PrintFmtRenderContext): Record<string, string>;
|
|
44
|
+
export declare function renderPrintFmt(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap, context?: PrintFmtRenderContext): string;
|
|
32
45
|
export {};
|
package/dist/trace/printf.js
CHANGED
|
@@ -2,8 +2,42 @@
|
|
|
2
2
|
* printf / fieldDict 格式化与 print fmt 渲染。
|
|
3
3
|
* @module traceFormat
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
const
|
|
5
|
+
/** printf 格式串解析缓存上限(LRU 淘汰) */
|
|
6
|
+
export const PRINTF_CACHE_MAX_SIZE = 256;
|
|
7
|
+
class LruCache {
|
|
8
|
+
maxSize;
|
|
9
|
+
map = new Map();
|
|
10
|
+
constructor(maxSize) {
|
|
11
|
+
this.maxSize = maxSize;
|
|
12
|
+
}
|
|
13
|
+
get(key) {
|
|
14
|
+
const value = this.map.get(key);
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
this.map.delete(key);
|
|
18
|
+
this.map.set(key, value);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
set(key, value) {
|
|
22
|
+
if (this.map.has(key))
|
|
23
|
+
this.map.delete(key);
|
|
24
|
+
this.map.set(key, value);
|
|
25
|
+
while (this.map.size > this.maxSize) {
|
|
26
|
+
const oldest = this.map.keys().next().value;
|
|
27
|
+
if (oldest === undefined)
|
|
28
|
+
break;
|
|
29
|
+
this.map.delete(oldest);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
get size() {
|
|
33
|
+
return this.map.size;
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
this.map.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const PRINT_FMT_TOKEN_CACHE = new LruCache(PRINTF_CACHE_MAX_SIZE);
|
|
40
|
+
const PRINT_FMT_SPEC_CACHE = new LruCache(PRINTF_CACHE_MAX_SIZE);
|
|
7
41
|
const NORMALIZED_PRINT_ARGS_CACHE = new WeakMap();
|
|
8
42
|
const EMPTY_PRINT_ARGS = [];
|
|
9
43
|
const PRINT_FLAG_CHARS = new Set(["-", "+", "#", "0", " "]);
|
|
@@ -24,6 +58,47 @@ function toBigIntForPrintf(value) {
|
|
|
24
58
|
function asUInt64(n) {
|
|
25
59
|
return n & ((1n << 64n) - 1n);
|
|
26
60
|
}
|
|
61
|
+
/** 按长度修饰符截断无符号值(C printf 语义) */
|
|
62
|
+
function maskUnsignedByLength(n, length) {
|
|
63
|
+
switch (length) {
|
|
64
|
+
case "hh":
|
|
65
|
+
return n & 0xffn;
|
|
66
|
+
case "h":
|
|
67
|
+
return n & 0xffffn;
|
|
68
|
+
case "l":
|
|
69
|
+
case "ll":
|
|
70
|
+
case "j":
|
|
71
|
+
case "z":
|
|
72
|
+
case "t":
|
|
73
|
+
case "L":
|
|
74
|
+
return asUInt64(n);
|
|
75
|
+
default:
|
|
76
|
+
return n & 0xffffffffn;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function signExtend(value, bits) {
|
|
80
|
+
const mask = (1n << BigInt(bits)) - 1n;
|
|
81
|
+
const v = value & mask;
|
|
82
|
+
const signBit = 1n << BigInt(bits - 1);
|
|
83
|
+
return v >= signBit ? v - (1n << BigInt(bits)) : v;
|
|
84
|
+
}
|
|
85
|
+
/** 按长度修饰符截断并符号扩展有符号值(C printf 语义) */
|
|
86
|
+
function maskSignedByLength(n, length) {
|
|
87
|
+
switch (length) {
|
|
88
|
+
case "hh":
|
|
89
|
+
return signExtend(n, 8);
|
|
90
|
+
case "h":
|
|
91
|
+
return signExtend(n, 16);
|
|
92
|
+
case "l":
|
|
93
|
+
case "ll":
|
|
94
|
+
case "j":
|
|
95
|
+
case "z":
|
|
96
|
+
case "t":
|
|
97
|
+
return signExtend(asUInt64(n), 64);
|
|
98
|
+
default:
|
|
99
|
+
return signExtend(n & 0xffffffffn, 32);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
27
102
|
function applyWidth(s, width, leftAlign, zeroPad) {
|
|
28
103
|
if (width === undefined || s.length >= width)
|
|
29
104
|
return s;
|
|
@@ -37,9 +112,15 @@ function applyWidth(s, width, leftAlign, zeroPad) {
|
|
|
37
112
|
function formatPrintfD(n, spec) {
|
|
38
113
|
const neg = n < 0n;
|
|
39
114
|
const abs = neg ? -n : n;
|
|
40
|
-
let digits = abs.toString(10);
|
|
41
115
|
const prec = spec.precision !== undefined ? spec.precision : 1;
|
|
42
|
-
|
|
116
|
+
let digits;
|
|
117
|
+
if (prec === 0 && abs === 0n) {
|
|
118
|
+
digits = "";
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
digits = abs.toString(10);
|
|
122
|
+
digits = digits.padStart(Math.max(prec, digits.length), "0");
|
|
123
|
+
}
|
|
43
124
|
let sign = "";
|
|
44
125
|
if (neg)
|
|
45
126
|
sign = "-";
|
|
@@ -61,24 +142,52 @@ function formatPrintfD(n, spec) {
|
|
|
61
142
|
return " ".repeat(padLen) + body;
|
|
62
143
|
}
|
|
63
144
|
function formatPrintfUnsignedRadix(unsigned, spec, radix, upper) {
|
|
64
|
-
let
|
|
65
|
-
? upper
|
|
66
|
-
? unsigned.toString(16).toUpperCase()
|
|
67
|
-
: unsigned.toString(16)
|
|
68
|
-
: unsigned.toString(radix);
|
|
145
|
+
let prefix = "";
|
|
69
146
|
const precDefault = 1;
|
|
70
147
|
const prec = spec.precision !== undefined ? spec.precision : precDefault;
|
|
71
|
-
|
|
148
|
+
let body;
|
|
149
|
+
if (prec === 0 && unsigned === 0n) {
|
|
150
|
+
body = "";
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
body =
|
|
154
|
+
radix === 16
|
|
155
|
+
? upper
|
|
156
|
+
? unsigned.toString(16).toUpperCase()
|
|
157
|
+
: unsigned.toString(16)
|
|
158
|
+
: unsigned.toString(radix);
|
|
159
|
+
body = body.padStart(Math.max(prec, body.length), "0");
|
|
160
|
+
}
|
|
72
161
|
if (hasPrintfFlag(spec.flags, "#")) {
|
|
73
|
-
if (radix === 16)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
162
|
+
if (radix === 16 && unsigned !== 0n) {
|
|
163
|
+
prefix = upper ? "0X" : "0x";
|
|
164
|
+
}
|
|
165
|
+
else if (radix === 8) {
|
|
166
|
+
if (unsigned === 0n && (prec === 0 || body === "")) {
|
|
167
|
+
body = "0";
|
|
168
|
+
}
|
|
169
|
+
else if (body === "") {
|
|
170
|
+
body = "0";
|
|
171
|
+
}
|
|
172
|
+
else if (!body.startsWith("0")) {
|
|
173
|
+
body = "0" + body;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
77
176
|
}
|
|
78
177
|
const zeroPad = hasPrintfFlag(spec.flags, "0") &&
|
|
79
178
|
!hasPrintfFlag(spec.flags, "-") &&
|
|
80
179
|
spec.precision === undefined;
|
|
81
|
-
|
|
180
|
+
const leftAlign = hasPrintfFlag(spec.flags, "-");
|
|
181
|
+
if (zeroPad && prefix) {
|
|
182
|
+
const width = spec.width;
|
|
183
|
+
if (width !== undefined) {
|
|
184
|
+
const bodyWidth = Math.max(width - prefix.length, body.length);
|
|
185
|
+
body = body.padStart(bodyWidth, "0");
|
|
186
|
+
}
|
|
187
|
+
return prefix + body;
|
|
188
|
+
}
|
|
189
|
+
const combined = prefix + body;
|
|
190
|
+
return applyWidth(combined, spec.width, leftAlign, zeroPad);
|
|
82
191
|
}
|
|
83
192
|
/**
|
|
84
193
|
* 按 C printf 子集格式化:标志 -+ #0 空格,宽度,精度,长度 hl ll hh,转换 d i u x X o s p。
|
|
@@ -106,14 +215,20 @@ export function formatPrintfValue(value, spec) {
|
|
|
106
215
|
return applyWidth(body, spec.width, hasPrintfFlag(spec.flags, "-"), zeroPad);
|
|
107
216
|
}
|
|
108
217
|
if (cl === "d" || cl === "i") {
|
|
109
|
-
return formatPrintfD(n, spec);
|
|
218
|
+
return formatPrintfD(maskSignedByLength(n, spec.length), spec);
|
|
110
219
|
}
|
|
111
220
|
if (cl === "u" || cl === "x" || cl === "X" || cl === "o") {
|
|
112
|
-
const u =
|
|
221
|
+
const u = maskUnsignedByLength(n, spec.length);
|
|
113
222
|
if (cl === "u") {
|
|
114
|
-
let body = u.toString(10);
|
|
115
223
|
const prec = spec.precision !== undefined ? spec.precision : 1;
|
|
116
|
-
|
|
224
|
+
let body;
|
|
225
|
+
if (prec === 0 && u === 0n) {
|
|
226
|
+
body = "";
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
body = u.toString(10);
|
|
230
|
+
body = body.padStart(Math.max(prec, body.length), "0");
|
|
231
|
+
}
|
|
117
232
|
const zeroPad = hasPrintfFlag(spec.flags, "0") &&
|
|
118
233
|
!hasPrintfFlag(spec.flags, "-") &&
|
|
119
234
|
spec.precision === undefined;
|
|
@@ -228,6 +343,16 @@ export function tokenizePrintfFormat(printFmt) {
|
|
|
228
343
|
flushText();
|
|
229
344
|
return out;
|
|
230
345
|
}
|
|
346
|
+
function assertPrintArgCount(printFmt, printArgs, context) {
|
|
347
|
+
const specCount = extractPrintfSpecs(printFmt).length;
|
|
348
|
+
const argCount = printArgs?.length ?? 0;
|
|
349
|
+
if (argCount < specCount) {
|
|
350
|
+
const eventLabel = context?.eventName !== undefined
|
|
351
|
+
? `"${context.eventName}"${context.eventId !== undefined ? ` (ID ${context.eventId})` : ""}`
|
|
352
|
+
: "unknown event";
|
|
353
|
+
throw new Error(`Print fmt for event ${eventLabel} requires ${specCount} args but got ${argCount} at spec index ${argCount}: "${printFmt}"`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
231
356
|
function extractPrintfSpecs(printFmt) {
|
|
232
357
|
const cached = PRINT_FMT_SPEC_CACHE.get(printFmt);
|
|
233
358
|
if (cached)
|
|
@@ -246,12 +371,34 @@ function getCachedPrintfTokens(printFmt) {
|
|
|
246
371
|
PRINT_FMT_TOKEN_CACHE.set(printFmt, tokens);
|
|
247
372
|
return tokens;
|
|
248
373
|
}
|
|
374
|
+
/** 测试专用:重置 printf 解析缓存 */
|
|
375
|
+
export function resetPrintfCachesForTest() {
|
|
376
|
+
PRINT_FMT_TOKEN_CACHE.clear();
|
|
377
|
+
PRINT_FMT_SPEC_CACHE.clear();
|
|
378
|
+
}
|
|
379
|
+
/** 测试专用:读取 printf 解析缓存条目数 */
|
|
380
|
+
export function getPrintfCacheSizesForTest() {
|
|
381
|
+
return { tokens: PRINT_FMT_TOKEN_CACHE.size, specs: PRINT_FMT_SPEC_CACHE.size };
|
|
382
|
+
}
|
|
249
383
|
function normalizePrintExpr(exprRaw) {
|
|
250
384
|
let expr = exprRaw.trim();
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
385
|
+
if (expr.startsWith("(") && expr.endsWith(")")) {
|
|
386
|
+
let depth = 0;
|
|
387
|
+
let fullyWrapped = true;
|
|
388
|
+
for (let i = 0; i < expr.length; i++) {
|
|
389
|
+
if (expr[i] === "(")
|
|
390
|
+
depth++;
|
|
391
|
+
else if (expr[i] === ")")
|
|
392
|
+
depth--;
|
|
393
|
+
if (depth === 0 && i < expr.length - 1) {
|
|
394
|
+
fullyWrapped = false;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (fullyWrapped && depth === 0) {
|
|
399
|
+
expr = expr.slice(1, -1).trim();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
255
402
|
expr = expr.replace(/\s+/g, "");
|
|
256
403
|
return expr;
|
|
257
404
|
}
|
|
@@ -267,7 +414,29 @@ function getNormalizedPrintArgs(printArgs) {
|
|
|
267
414
|
}
|
|
268
415
|
function evalPrintArgExpr(exprRaw, fieldMap) {
|
|
269
416
|
const expr = normalizePrintExpr(exprRaw);
|
|
270
|
-
let m = expr.match(/^
|
|
417
|
+
let m = expr.match(/^__get_str\((\w+)\)$/);
|
|
418
|
+
if (m) {
|
|
419
|
+
const v = fieldMap[m[1]];
|
|
420
|
+
if (typeof v === "string")
|
|
421
|
+
return v;
|
|
422
|
+
throw new Error(`__get_str(${m[1]}) requires a string field, got ${typeof v}`);
|
|
423
|
+
}
|
|
424
|
+
m = expr.match(/^__get_dynamic_array\((\w+)\)(?:\[(\d+)])?$/);
|
|
425
|
+
if (m) {
|
|
426
|
+
const v = fieldMap[m[1]];
|
|
427
|
+
if (!Array.isArray(v)) {
|
|
428
|
+
throw new Error(`__get_dynamic_array(${m[1]}) requires an array field, got ${typeof v}`);
|
|
429
|
+
}
|
|
430
|
+
if (m[2] !== undefined) {
|
|
431
|
+
const idx = parseInt(m[2], 10);
|
|
432
|
+
if (idx < 0 || idx >= v.length) {
|
|
433
|
+
throw new Error(`Invalid print arg index for __get_dynamic_array(${m[1]})[${m[2]}]`);
|
|
434
|
+
}
|
|
435
|
+
return v[idx];
|
|
436
|
+
}
|
|
437
|
+
return v;
|
|
438
|
+
}
|
|
439
|
+
m = expr.match(/^REC->(\w+)&0xffff$/);
|
|
271
440
|
if (m) {
|
|
272
441
|
const v = fieldMap[m[1]];
|
|
273
442
|
const n = typeof v === "number" ? v : 0;
|
|
@@ -280,32 +449,49 @@ function evalPrintArgExpr(exprRaw, fieldMap) {
|
|
|
280
449
|
return (n >>> 16) & 0xffff;
|
|
281
450
|
}
|
|
282
451
|
m = expr.match(/^REC->(\w+)(?:\[(\d+)])?$/);
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
452
|
+
if (m) {
|
|
453
|
+
const name = m[1];
|
|
454
|
+
const idxRaw = m[2];
|
|
455
|
+
const v = fieldMap[name];
|
|
456
|
+
if (idxRaw !== undefined) {
|
|
457
|
+
const idx = parseInt(idxRaw, 10);
|
|
458
|
+
if (Array.isArray(v) && idx >= 0 && idx < v.length) {
|
|
459
|
+
return v[idx];
|
|
460
|
+
}
|
|
461
|
+
throw new Error(`Invalid print arg index for REC->${name}[${idxRaw}]`);
|
|
292
462
|
}
|
|
293
|
-
|
|
463
|
+
if (Array.isArray(v))
|
|
464
|
+
return v[0] ?? 0;
|
|
465
|
+
if (v !== undefined)
|
|
466
|
+
return v;
|
|
467
|
+
throw new Error(`Missing field for print arg REC->${name}`);
|
|
294
468
|
}
|
|
295
|
-
|
|
296
|
-
return v[0] ?? 0;
|
|
297
|
-
return v ?? 0;
|
|
469
|
+
throw new Error(`Unknown print arg expression: ${exprRaw.trim()}`);
|
|
298
470
|
}
|
|
299
471
|
function buildPrintArgValues(printArgs, fieldMap) {
|
|
300
472
|
const normalizedArgs = getNormalizedPrintArgs(printArgs);
|
|
301
473
|
const values = (printArgs ?? []).map((e) => evalPrintArgExpr(e, fieldMap));
|
|
302
474
|
return { normalizedArgs, values };
|
|
303
475
|
}
|
|
476
|
+
function resolvePrintArgValue(value) {
|
|
477
|
+
if (value === undefined)
|
|
478
|
+
return 0;
|
|
479
|
+
if (Array.isArray(value))
|
|
480
|
+
return value[0] ?? 0;
|
|
481
|
+
return value;
|
|
482
|
+
}
|
|
304
483
|
function formatOnePrintArg(idx, spec, fieldMap, normalizedArgs, values) {
|
|
305
|
-
const v = values[idx]
|
|
484
|
+
const v = resolvePrintArgValue(values[idx]);
|
|
306
485
|
const argExpr = normalizedArgs[idx] ?? "";
|
|
307
486
|
if (spec.conv === "s" || spec.conv === "S") {
|
|
308
|
-
|
|
487
|
+
let m = argExpr.match(/^__get_str\((\w+)\)$/);
|
|
488
|
+
if (m) {
|
|
489
|
+
const s = fieldMap[m[1]];
|
|
490
|
+
if (typeof s === "string") {
|
|
491
|
+
return formatPrintfValue(s, spec);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
m = argExpr.match(/^REC->__data_loc_(\w+)(?:&0xffff)?(?:>>16)?$/);
|
|
309
495
|
if (m) {
|
|
310
496
|
const s = fieldMap[m[1]];
|
|
311
497
|
if (typeof s === "string") {
|
|
@@ -319,6 +505,14 @@ function fieldKeyFromNormalizedArg(normalizedArg, index) {
|
|
|
319
505
|
let k = normalizedArg;
|
|
320
506
|
if (k.startsWith("REC->"))
|
|
321
507
|
k = k.slice(5);
|
|
508
|
+
const mGetStr = k.match(/^__get_str\((\w+)\)$/);
|
|
509
|
+
if (mGetStr) {
|
|
510
|
+
return mGetStr[1];
|
|
511
|
+
}
|
|
512
|
+
const mGetArr = k.match(/^__get_dynamic_array\((\w+)\)(?:\[(\d+)])?$/);
|
|
513
|
+
if (mGetArr) {
|
|
514
|
+
return mGetArr[2] !== undefined ? `${mGetArr[1]}__${mGetArr[2]}` : mGetArr[1];
|
|
515
|
+
}
|
|
322
516
|
// REC->__data_loc_path&0xffff 在 fieldDict 中收敛为 path 作为键名
|
|
323
517
|
const mDataLocOff = k.match(/^__data_loc_(\w+)&0xffff$/);
|
|
324
518
|
if (mDataLocOff) {
|
|
@@ -326,7 +520,8 @@ function fieldKeyFromNormalizedArg(normalizedArg, index) {
|
|
|
326
520
|
}
|
|
327
521
|
return k.length > 0 ? k : `arg${index}`;
|
|
328
522
|
}
|
|
329
|
-
export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
|
|
523
|
+
export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap, context) {
|
|
524
|
+
assertPrintArgCount(printFmt, printArgs, context);
|
|
330
525
|
const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
|
|
331
526
|
const specs = extractPrintfSpecs(printFmt);
|
|
332
527
|
const out = {};
|
|
@@ -340,7 +535,8 @@ export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
|
|
|
340
535
|
}
|
|
341
536
|
return out;
|
|
342
537
|
}
|
|
343
|
-
export function renderPrintFmt(printFmt, printArgs, fieldMap) {
|
|
538
|
+
export function renderPrintFmt(printFmt, printArgs, fieldMap, context) {
|
|
539
|
+
assertPrintArgCount(printFmt, printArgs, context);
|
|
344
540
|
const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
|
|
345
541
|
const tokens = getCachedPrintfTokens(printFmt);
|
|
346
542
|
let argIdx = 0;
|