ya-struct 0.0.11 → 0.0.13
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/lib/index.d.ts +2 -1
- package/dist/lib/index.js +3 -1
- package/dist/lib/layout.d.ts +7 -2
- package/dist/lib/layout.js +112 -16
- package/dist/lib/parser.d.ts +28 -5
- package/dist/lib/parser.js +27 -4
- package/dist/lib/tests/compile-and-compare.vibe.d.ts +30 -0
- package/dist/lib/tests/compile-and-compare.vibe.js +225 -0
- package/dist/lib/types/index.d.ts +7 -2
- package/dist/lib/types/index.js +9 -1
- package/dist/lib/types/struct.js +10 -2
- package/package.json +1 -1
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { define } from "./parser.js";
|
|
2
2
|
import { types } from "./types/index.js";
|
|
3
|
+
import { compileAndCompare } from "./tests/compile-and-compare.vibe.js";
|
|
3
4
|
import type { TAbi, TCompiler, TDataModel, TEndianness } from "./common.js";
|
|
4
|
-
export { define, types };
|
|
5
|
+
export { define, types, compileAndCompare };
|
|
5
6
|
export type { TAbi, TEndianness, TCompiler, TDataModel };
|
package/dist/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { define } from "./parser.js";
|
|
2
2
|
import { types } from "./types/index.js";
|
|
3
|
+
import { compileAndCompare } from "./tests/compile-and-compare.vibe.js";
|
|
3
4
|
|
|
4
5
|
/*import type {
|
|
5
6
|
TAbi,
|
|
@@ -10,7 +11,8 @@ import { types } from "./types/index.js";
|
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
define,
|
|
13
|
-
types
|
|
14
|
+
types,
|
|
15
|
+
compileAndCompare
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
/*export type {
|
package/dist/lib/layout.d.ts
CHANGED
|
@@ -26,10 +26,15 @@ type TLayoutedField = {
|
|
|
26
26
|
readonly type: "struct";
|
|
27
27
|
readonly offsetInBits: number;
|
|
28
28
|
readonly sizeInBits: number;
|
|
29
|
-
readonly fields: readonly {
|
|
29
|
+
readonly fields: readonly ({
|
|
30
|
+
readonly pad?: false;
|
|
30
31
|
readonly name: string;
|
|
31
32
|
readonly definition: TLayoutedField;
|
|
32
|
-
}
|
|
33
|
+
} | {
|
|
34
|
+
readonly pad: true;
|
|
35
|
+
readonly name: string | undefined;
|
|
36
|
+
readonly definition: TLayoutedField;
|
|
37
|
+
})[];
|
|
33
38
|
readonly packed: boolean;
|
|
34
39
|
readonly fixedAbi: Partial<TAbi>;
|
|
35
40
|
} | {
|
package/dist/lib/layout.js
CHANGED
|
@@ -29,7 +29,15 @@ import nodeUtil from "node:util";
|
|
|
29
29
|
readonly type: "struct";
|
|
30
30
|
readonly offsetInBits: number;
|
|
31
31
|
readonly sizeInBits: number;
|
|
32
|
-
readonly fields: readonly {
|
|
32
|
+
readonly fields: readonly ({
|
|
33
|
+
readonly pad?: false;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly definition: TLayoutedField;
|
|
36
|
+
} | {
|
|
37
|
+
readonly pad: true;
|
|
38
|
+
readonly name: string | undefined;
|
|
39
|
+
readonly definition: TLayoutedField;
|
|
40
|
+
})[];
|
|
33
41
|
readonly packed: boolean;
|
|
34
42
|
readonly fixedAbi: Partial<TAbi>;
|
|
35
43
|
} | {
|
|
@@ -51,6 +59,83 @@ const pointerSizeInBitsByDataModel = ({ dataModel }/*: { dataModel: TAbi["dataMo
|
|
|
51
59
|
}
|
|
52
60
|
};
|
|
53
61
|
|
|
62
|
+
// eslint-disable-next-line max-statements, complexity
|
|
63
|
+
const alignmentOfField = ({ definition, abi }/*: { definition: TFieldType, abi: TAbi }*/)/*: number*/ => {
|
|
64
|
+
if (definition.type === "c-type") {
|
|
65
|
+
const cTypeNormalizer = createCTypeNormalizer({ abi });
|
|
66
|
+
const normalizedField = cTypeNormalizer.normalize({ cField: definition });
|
|
67
|
+
return alignmentOfField({ definition: normalizedField, abi });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (definition.type === "integer" || definition.type === "float") {
|
|
71
|
+
const sizeInBits = definition.sizeInBits;
|
|
72
|
+
if (abi.compiler === "gcc" && abi.dataModel === "ILP32" && sizeInBits === 64) {
|
|
73
|
+
return 32;
|
|
74
|
+
}
|
|
75
|
+
return sizeInBits;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (definition.type === "pointer") {
|
|
79
|
+
return pointerSizeInBitsByDataModel({ dataModel: abi.dataModel });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (definition.type === "array") {
|
|
83
|
+
return alignmentOfField({ definition: definition.elementType, abi });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (definition.type === "struct") {
|
|
87
|
+
if (definition.packed) {
|
|
88
|
+
return 8;
|
|
89
|
+
}
|
|
90
|
+
if (definition.fields.length === 0) {
|
|
91
|
+
return 8;
|
|
92
|
+
}
|
|
93
|
+
return Math.max(...definition.fields.map((f) => {
|
|
94
|
+
return alignmentOfField({ definition: f.definition, abi });
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (definition.type === "string") {
|
|
99
|
+
return definition.charSizeInBits;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
throw Error(`unsupported field type for alignment calculation`);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// eslint-disable-next-line complexity
|
|
106
|
+
const translateLayoutOffset = ({ field, offset }/*: { field: TLayoutedField; offset: number }*/)/*: TLayoutedField*/ => {
|
|
107
|
+
if (offset === 0) {
|
|
108
|
+
return field;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
switch (field.type) {
|
|
112
|
+
case "integer":
|
|
113
|
+
return { ...field, offsetInBits: field.offsetInBits + offset };
|
|
114
|
+
case "float":
|
|
115
|
+
return { ...field, offsetInBits: field.offsetInBits + offset };
|
|
116
|
+
case "pointer":
|
|
117
|
+
return { ...field, offsetInBits: field.offsetInBits + offset };
|
|
118
|
+
case "string":
|
|
119
|
+
return { ...field, offsetInBits: field.offsetInBits + offset };
|
|
120
|
+
case "array":
|
|
121
|
+
return { ...field, offsetInBits: field.offsetInBits + offset };
|
|
122
|
+
case "struct":
|
|
123
|
+
return {
|
|
124
|
+
...field,
|
|
125
|
+
offsetInBits: field.offsetInBits + offset,
|
|
126
|
+
fields: field.fields.map((f) => {
|
|
127
|
+
const translated = translateLayoutOffset({ field: f.definition, offset });
|
|
128
|
+
if (f.pad) {
|
|
129
|
+
return { pad: true /*as const*/, name: f.name, definition: translated };
|
|
130
|
+
}
|
|
131
|
+
return { name: f.name, definition: translated };
|
|
132
|
+
})
|
|
133
|
+
};
|
|
134
|
+
default:
|
|
135
|
+
throw Error(`unsupported field type for layout offset translation`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
54
139
|
const layoutStruct = ({
|
|
55
140
|
definition,
|
|
56
141
|
abi,
|
|
@@ -61,13 +146,13 @@ const layoutStruct = ({
|
|
|
61
146
|
currentOffsetInBits: number
|
|
62
147
|
}*/)/*: TLayoutedField*/ => {
|
|
63
148
|
|
|
64
|
-
|
|
65
|
-
const structAlignmentInBits = 64;
|
|
66
|
-
// const fieldAlignmentInBits = 64;
|
|
149
|
+
const structAlignmentInBits = definition.packed ? 8 : alignmentOfField({ definition, abi });
|
|
67
150
|
const fieldAlignmentInBits = 1;
|
|
68
151
|
const pointerSizeInBits = pointerSizeInBitsByDataModel({ dataModel: abi.dataModel });
|
|
69
152
|
|
|
70
|
-
|
|
153
|
+
// Compute internal layout from offset 0 to ensure correct internal alignment
|
|
154
|
+
// independent of the struct's placement position
|
|
155
|
+
let currentOffsetInBits = 0;
|
|
71
156
|
let layoutedFields/*: (TLayoutedField & { type: "struct" })["fields"]*/ = [];
|
|
72
157
|
|
|
73
158
|
// eslint-disable-next-line max-statements,complexity
|
|
@@ -112,11 +197,8 @@ const layoutStruct = ({
|
|
|
112
197
|
break;
|
|
113
198
|
}
|
|
114
199
|
case "struct": {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
throw Error("nested struct alignment handling not implemented yet");
|
|
118
|
-
}
|
|
119
|
-
|
|
200
|
+
const nestedAlignment = alignmentOfField({ definition: normalizedField, abi });
|
|
201
|
+
currentOffsetInBits = align({ offset: currentOffsetInBits, alignment: nestedAlignment });
|
|
120
202
|
break;
|
|
121
203
|
}
|
|
122
204
|
case "string": {
|
|
@@ -137,20 +219,34 @@ const layoutStruct = ({
|
|
|
137
219
|
|
|
138
220
|
layoutedFields = [
|
|
139
221
|
...layoutedFields,
|
|
140
|
-
|
|
141
|
-
name: field.name,
|
|
142
|
-
definition: fieldLayout
|
|
143
|
-
}
|
|
222
|
+
field.pad
|
|
223
|
+
? { pad: true /*as const*/, name: field.name, definition: fieldLayout }
|
|
224
|
+
: { name: field.name, definition: fieldLayout }
|
|
144
225
|
];
|
|
145
226
|
|
|
146
227
|
currentOffsetInBits = align({ offset: fieldLayout.offsetInBits + fieldLayout.sizeInBits, alignment: fieldAlignmentInBits });
|
|
147
228
|
});
|
|
148
229
|
|
|
230
|
+
if (!definition.packed) {
|
|
231
|
+
currentOffsetInBits = align({ offset: currentOffsetInBits, alignment: structAlignmentInBits });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const sizeInBits = currentOffsetInBits;
|
|
235
|
+
|
|
236
|
+
// Translate all field offsets to the actual placement position
|
|
237
|
+
const translatedFields = layoutedFields.map((f) => {
|
|
238
|
+
const translated = translateLayoutOffset({ field: f.definition, offset: initialOffsetInBits });
|
|
239
|
+
if (f.pad) {
|
|
240
|
+
return { pad: true /*as const*/, name: f.name, definition: translated };
|
|
241
|
+
}
|
|
242
|
+
return { name: f.name, definition: translated };
|
|
243
|
+
});
|
|
244
|
+
|
|
149
245
|
return {
|
|
150
246
|
type: "struct",
|
|
151
247
|
offsetInBits: initialOffsetInBits,
|
|
152
|
-
sizeInBits
|
|
153
|
-
fields:
|
|
248
|
+
sizeInBits,
|
|
249
|
+
fields: translatedFields,
|
|
154
250
|
packed: definition.packed,
|
|
155
251
|
fixedAbi: definition.fixedAbi
|
|
156
252
|
};
|
package/dist/lib/parser.d.ts
CHANGED
|
@@ -16,20 +16,42 @@ type FieldValue<T extends TFieldType> = T extends {
|
|
|
16
16
|
} ? FieldValue<E & TFieldType>[] : T extends {
|
|
17
17
|
type: "struct";
|
|
18
18
|
fields: infer F;
|
|
19
|
-
} ? StructValue<F & readonly {
|
|
19
|
+
} ? StructValue<F & readonly ({
|
|
20
|
+
pad?: false | undefined;
|
|
20
21
|
name: string;
|
|
21
22
|
definition: TFieldType;
|
|
22
|
-
}
|
|
23
|
+
} | {
|
|
24
|
+
pad: true;
|
|
25
|
+
name: string | undefined;
|
|
26
|
+
definition: TFieldType;
|
|
27
|
+
})[]> : T extends {
|
|
23
28
|
type: "c-type";
|
|
24
29
|
cType: "float" | "double" | "long double";
|
|
25
30
|
} ? number : T extends {
|
|
26
31
|
type: "c-type";
|
|
27
32
|
} ? bigint : never;
|
|
28
|
-
type
|
|
33
|
+
type NonPadFields<F extends readonly ({
|
|
34
|
+
pad?: false | undefined;
|
|
35
|
+
name: string;
|
|
36
|
+
definition: TFieldType;
|
|
37
|
+
} | {
|
|
38
|
+
pad: true;
|
|
39
|
+
name: string | undefined;
|
|
40
|
+
definition: TFieldType;
|
|
41
|
+
})[]> = Extract<F[number], {
|
|
42
|
+
pad?: false | undefined;
|
|
43
|
+
name: string;
|
|
44
|
+
}>;
|
|
45
|
+
type StructValue<F extends readonly ({
|
|
46
|
+
pad?: false | undefined;
|
|
29
47
|
name: string;
|
|
30
48
|
definition: TFieldType;
|
|
31
|
-
}
|
|
32
|
-
|
|
49
|
+
} | {
|
|
50
|
+
pad: true;
|
|
51
|
+
name: string | undefined;
|
|
52
|
+
definition: TFieldType;
|
|
53
|
+
})[]> = {
|
|
54
|
+
[K in NonPadFields<F> as K["name"]]: FieldValue<K["definition"]>;
|
|
33
55
|
};
|
|
34
56
|
type Simplify<T> = {
|
|
35
57
|
[K in keyof T]: T[K];
|
|
@@ -54,3 +76,4 @@ declare const define: <const T extends TFieldType>({ definition }: {
|
|
|
54
76
|
}) => TParser<T>;
|
|
55
77
|
};
|
|
56
78
|
export { define };
|
|
79
|
+
export type { TParser, };
|
package/dist/lib/parser.js
CHANGED
|
@@ -12,15 +12,34 @@ import { createStructParser } from "./types/struct.js";
|
|
|
12
12
|
T extends { type: "array"; elementType: infer E; length: number }
|
|
13
13
|
? FieldValue<E & TFieldType>[] :
|
|
14
14
|
T extends { type: "struct"; fields: infer F }
|
|
15
|
-
? StructValue<
|
|
16
|
-
|
|
15
|
+
? StructValue<
|
|
16
|
+
F & readonly (
|
|
17
|
+
| { pad?: false | undefined; name: string; definition: TFieldType }
|
|
18
|
+
| { pad: true; name: string | undefined; definition: TFieldType }
|
|
19
|
+
)[]
|
|
20
|
+
> :
|
|
21
|
+
T extends {
|
|
22
|
+
type: "c-type";
|
|
23
|
+
cType: "float" | "double" | "long double";
|
|
24
|
+
} ? number :
|
|
17
25
|
T extends { type: "c-type" } ? bigint :
|
|
18
26
|
never;*/
|
|
19
27
|
|
|
28
|
+
/*type NonPadFields<
|
|
29
|
+
F extends readonly (
|
|
30
|
+
| { pad?: false | undefined; name: string; definition: TFieldType }
|
|
31
|
+
| { pad: true; name: string | undefined; definition: TFieldType }
|
|
32
|
+
)[]
|
|
33
|
+
> =
|
|
34
|
+
Extract<F[number], { pad?: false | undefined; name: string }>;*/
|
|
35
|
+
|
|
20
36
|
/*type StructValue<
|
|
21
|
-
F extends readonly
|
|
37
|
+
F extends readonly (
|
|
38
|
+
| { pad?: false | undefined; name: string; definition: TFieldType }
|
|
39
|
+
| { pad: true; name: string | undefined; definition: TFieldType }
|
|
40
|
+
)[]
|
|
22
41
|
> = {
|
|
23
|
-
[K in F
|
|
42
|
+
[K in NonPadFields<F> as K["name"]]: FieldValue<K["definition"]>;
|
|
24
43
|
};*/
|
|
25
44
|
|
|
26
45
|
/*type Simplify<T> = {
|
|
@@ -88,3 +107,7 @@ const define = /*<const T extends TFieldType>*/({ definition }/*: { definition:
|
|
|
88
107
|
export {
|
|
89
108
|
define
|
|
90
109
|
};
|
|
110
|
+
|
|
111
|
+
/*export type {
|
|
112
|
+
TParser,
|
|
113
|
+
};*/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TAbi } from "../common.js";
|
|
2
|
+
import type { TFieldType } from "../types/index.js";
|
|
3
|
+
type TLayoutError = {
|
|
4
|
+
type: "size-mismatch";
|
|
5
|
+
fieldPath: string;
|
|
6
|
+
expectedSizeInBits: number;
|
|
7
|
+
actualSizeInBits: number;
|
|
8
|
+
} | {
|
|
9
|
+
type: "offset-mismatch";
|
|
10
|
+
fieldPath: string;
|
|
11
|
+
expectedOffsetInBits: number;
|
|
12
|
+
actualOffsetInBits: number;
|
|
13
|
+
};
|
|
14
|
+
type TCompileAndCompareResult = {
|
|
15
|
+
layoutErrors: TLayoutError[];
|
|
16
|
+
};
|
|
17
|
+
declare const compileAndCompare: ({ structDefinition, abi, globalCode, cStructName, compileAndRun }: {
|
|
18
|
+
structDefinition: Extract<TFieldType, {
|
|
19
|
+
type: "struct";
|
|
20
|
+
}>;
|
|
21
|
+
abi: TAbi;
|
|
22
|
+
globalCode: string;
|
|
23
|
+
cStructName: string;
|
|
24
|
+
compileAndRun: ({ sourceCode }: {
|
|
25
|
+
sourceCode: string;
|
|
26
|
+
}) => Promise<{
|
|
27
|
+
output: string;
|
|
28
|
+
}>;
|
|
29
|
+
}) => Promise<TCompileAndCompareResult>;
|
|
30
|
+
export { compileAndCompare };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*import type { TAbi } from "../common.ts";*/
|
|
2
|
+
/*import type { TLayoutedField } from "../layout.ts";*/
|
|
3
|
+
import { define } from "../parser.js";
|
|
4
|
+
/*import type { TFieldType } from "../types/index.ts";*/
|
|
5
|
+
|
|
6
|
+
/*type TLayoutError = {
|
|
7
|
+
type: "size-mismatch",
|
|
8
|
+
fieldPath: string;
|
|
9
|
+
expectedSizeInBits: number;
|
|
10
|
+
actualSizeInBits: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: "offset-mismatch",
|
|
13
|
+
fieldPath: string;
|
|
14
|
+
expectedOffsetInBits: number;
|
|
15
|
+
actualOffsetInBits: number;
|
|
16
|
+
};*/
|
|
17
|
+
|
|
18
|
+
/*type TCompileAndCompareResult = {
|
|
19
|
+
layoutErrors: TLayoutError[];
|
|
20
|
+
};*/
|
|
21
|
+
|
|
22
|
+
/*type TFlatField = {
|
|
23
|
+
fieldPath: string;
|
|
24
|
+
offsetInBits: number;
|
|
25
|
+
sizeInBits: number;
|
|
26
|
+
isStruct: boolean;
|
|
27
|
+
};*/
|
|
28
|
+
|
|
29
|
+
/*type TStructLayoutedFields =
|
|
30
|
+
(TLayoutedField & { type: "struct" })["fields"];*/
|
|
31
|
+
|
|
32
|
+
const flattenLayout = ({
|
|
33
|
+
fields,
|
|
34
|
+
parentPath
|
|
35
|
+
}/*: {
|
|
36
|
+
fields: TStructLayoutedFields;
|
|
37
|
+
parentPath: string;
|
|
38
|
+
}*/)/*: TFlatField[]*/ => {
|
|
39
|
+
return fields.flatMap((field) => {
|
|
40
|
+
if (field.pad) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const fieldPath = parentPath
|
|
45
|
+
? `${parentPath}.${field.name}`
|
|
46
|
+
: field.name/*!*/;
|
|
47
|
+
|
|
48
|
+
const isStruct = field.definition.type === "struct";
|
|
49
|
+
|
|
50
|
+
const entry/*: TFlatField*/ = {
|
|
51
|
+
fieldPath,
|
|
52
|
+
offsetInBits: field.definition.offsetInBits,
|
|
53
|
+
sizeInBits: field.definition.sizeInBits,
|
|
54
|
+
isStruct,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (isStruct) {
|
|
58
|
+
return [
|
|
59
|
+
entry,
|
|
60
|
+
...flattenLayout({
|
|
61
|
+
fields: field.definition.fields,
|
|
62
|
+
parentPath: fieldPath,
|
|
63
|
+
}),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [entry];
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const generateSourceCode = ({
|
|
72
|
+
globalCode,
|
|
73
|
+
cStructName,
|
|
74
|
+
flatFields,
|
|
75
|
+
}/*: {
|
|
76
|
+
globalCode: string;
|
|
77
|
+
cStructName: string;
|
|
78
|
+
flatFields: TFlatField[];
|
|
79
|
+
}*/) => {
|
|
80
|
+
|
|
81
|
+
const printStatements = flatFields.map((f, idx) => {
|
|
82
|
+
if (f.isStruct) {
|
|
83
|
+
const offsetExpr =
|
|
84
|
+
`offsetof(struct ${cStructName}, ${f.fieldPath}) * CHAR_BIT`;
|
|
85
|
+
const sizeExpr =
|
|
86
|
+
`sizeof(((struct ${cStructName}*)0)->${f.fieldPath}) * CHAR_BIT`;
|
|
87
|
+
|
|
88
|
+
return ` printf("%zu %zu\\n", `
|
|
89
|
+
+ `(size_t)(${offsetExpr}), (size_t)(${sizeExpr}));`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Use memory scanning to detect actual bit offset and size.
|
|
93
|
+
// This works for both regular fields and bitfields.
|
|
94
|
+
return ` {
|
|
95
|
+
struct ${cStructName} __s${idx};
|
|
96
|
+
memset(&__s${idx}, 0, sizeof(__s${idx}));
|
|
97
|
+
memset(&__s${idx}.${f.fieldPath}, 0xFF, sizeof(__s${idx}.${f.fieldPath}));
|
|
98
|
+
unsigned char *__p${idx} = (unsigned char *)&__s${idx};
|
|
99
|
+
size_t __off${idx} = 0, __sz${idx} = 0;
|
|
100
|
+
int __found${idx} = 0;
|
|
101
|
+
for (size_t __i = 0; __i < sizeof(struct ${cStructName}) * CHAR_BIT; __i++) {
|
|
102
|
+
if (__p${idx}[__i / CHAR_BIT] & (1u << (__i % CHAR_BIT))) {
|
|
103
|
+
if (!__found${idx}) { __off${idx} = __i; __found${idx} = 1; }
|
|
104
|
+
__sz${idx}++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
printf("%zu %zu\\n", __off${idx}, __sz${idx});
|
|
108
|
+
}`;
|
|
109
|
+
}).join("\n");
|
|
110
|
+
|
|
111
|
+
return `#include <stdio.h>
|
|
112
|
+
#include <stddef.h>
|
|
113
|
+
#include <limits.h>
|
|
114
|
+
#include <string.h>
|
|
115
|
+
${globalCode}
|
|
116
|
+
|
|
117
|
+
int main(void) {
|
|
118
|
+
${printStatements}
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const parseCOutput = ({
|
|
125
|
+
output,
|
|
126
|
+
}/*: {
|
|
127
|
+
output: string;
|
|
128
|
+
}*/)/*: { offset: number; size: number }[]*/ => {
|
|
129
|
+
return output.trim().split("\n").map((line) => {
|
|
130
|
+
const parts = line.trim().split(" ");
|
|
131
|
+
return {
|
|
132
|
+
offset: parseInt(parts[0], 10),
|
|
133
|
+
size: parseInt(parts[1], 10),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const compareLayouts = ({
|
|
139
|
+
flatFields,
|
|
140
|
+
cFields,
|
|
141
|
+
}/*: {
|
|
142
|
+
flatFields: TFlatField[];
|
|
143
|
+
cFields: { offset: number; size: number }[];
|
|
144
|
+
}*/)/*: TLayoutError[]*/ => {
|
|
145
|
+
return flatFields.flatMap((field, idx) => {
|
|
146
|
+
const cField = cFields[idx];
|
|
147
|
+
if (!cField) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sizeMismatch/*: TLayoutError[]*/ =
|
|
152
|
+
cField.size === field.sizeInBits
|
|
153
|
+
? []
|
|
154
|
+
: [{
|
|
155
|
+
type: "size-mismatch" /*as const*/,
|
|
156
|
+
fieldPath: field.fieldPath,
|
|
157
|
+
expectedSizeInBits: cField.size,
|
|
158
|
+
actualSizeInBits: field.sizeInBits,
|
|
159
|
+
}];
|
|
160
|
+
|
|
161
|
+
const offsetMismatch/*: TLayoutError[]*/ =
|
|
162
|
+
cField.offset === field.offsetInBits
|
|
163
|
+
? []
|
|
164
|
+
: [{
|
|
165
|
+
type: "offset-mismatch" /*as const*/,
|
|
166
|
+
fieldPath: field.fieldPath,
|
|
167
|
+
expectedOffsetInBits: cField.offset,
|
|
168
|
+
actualOffsetInBits: field.offsetInBits,
|
|
169
|
+
}];
|
|
170
|
+
|
|
171
|
+
return [...sizeMismatch, ...offsetMismatch];
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const compileAndCompare = async ({
|
|
176
|
+
structDefinition,
|
|
177
|
+
abi,
|
|
178
|
+
|
|
179
|
+
globalCode,
|
|
180
|
+
cStructName,
|
|
181
|
+
|
|
182
|
+
compileAndRun
|
|
183
|
+
}/*: {
|
|
184
|
+
structDefinition: Extract<TFieldType, { type: "struct" }>;
|
|
185
|
+
abi: TAbi;
|
|
186
|
+
|
|
187
|
+
globalCode: string;
|
|
188
|
+
cStructName: string;
|
|
189
|
+
|
|
190
|
+
compileAndRun: ({ sourceCode }: { sourceCode: string }) => Promise<{
|
|
191
|
+
output: string;
|
|
192
|
+
}>;
|
|
193
|
+
}*/)/*: Promise<TCompileAndCompareResult>*/ => {
|
|
194
|
+
|
|
195
|
+
const def = define({ definition: structDefinition });
|
|
196
|
+
const parser = def.parser({ abi });
|
|
197
|
+
const layoutResult = parser.layout;
|
|
198
|
+
|
|
199
|
+
if (layoutResult.type !== "struct") {
|
|
200
|
+
throw Error("expected struct layout");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const flatFields = flattenLayout({
|
|
204
|
+
fields: layoutResult.fields,
|
|
205
|
+
parentPath: "",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const sourceCode = generateSourceCode({
|
|
209
|
+
globalCode,
|
|
210
|
+
cStructName,
|
|
211
|
+
flatFields,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const { output } = await compileAndRun({ sourceCode });
|
|
215
|
+
|
|
216
|
+
const cFields = parseCOutput({ output });
|
|
217
|
+
|
|
218
|
+
const layoutErrors = compareLayouts({ flatFields, cFields });
|
|
219
|
+
|
|
220
|
+
return { layoutErrors };
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
compileAndCompare
|
|
225
|
+
};
|
|
@@ -18,10 +18,15 @@ type TFieldType = {
|
|
|
18
18
|
readonly length: number;
|
|
19
19
|
} | {
|
|
20
20
|
readonly type: "struct";
|
|
21
|
-
readonly fields: readonly {
|
|
21
|
+
readonly fields: readonly ({
|
|
22
|
+
readonly pad?: false | undefined;
|
|
22
23
|
readonly name: string;
|
|
23
24
|
readonly definition: TFieldType;
|
|
24
|
-
}
|
|
25
|
+
} | {
|
|
26
|
+
readonly pad: true;
|
|
27
|
+
readonly name: string | undefined;
|
|
28
|
+
readonly definition: TFieldType;
|
|
29
|
+
})[];
|
|
25
30
|
readonly packed: boolean;
|
|
26
31
|
readonly fixedAbi: Partial<TAbi>;
|
|
27
32
|
} | {
|
package/dist/lib/types/index.js
CHANGED
|
@@ -26,7 +26,15 @@
|
|
|
26
26
|
readonly length: number;
|
|
27
27
|
} | {
|
|
28
28
|
readonly type: "struct";
|
|
29
|
-
readonly fields: readonly {
|
|
29
|
+
readonly fields: readonly ({
|
|
30
|
+
readonly pad?: false | undefined;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly definition: TFieldType
|
|
33
|
+
} | {
|
|
34
|
+
readonly pad: true;
|
|
35
|
+
readonly name: string | undefined;
|
|
36
|
+
readonly definition: TFieldType;
|
|
37
|
+
})[];
|
|
30
38
|
readonly packed: boolean;
|
|
31
39
|
readonly fixedAbi: Partial<TAbi>;
|
|
32
40
|
} | {
|
package/dist/lib/types/struct.js
CHANGED
|
@@ -78,6 +78,10 @@ const createStructParser = ({
|
|
|
78
78
|
let result/*: Record<string, unknown>*/ = {};
|
|
79
79
|
|
|
80
80
|
layoutedFields.forEach((field, idx) => {
|
|
81
|
+
if (field.pad) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
81
85
|
const fieldParser = fieldParsers[idx];
|
|
82
86
|
|
|
83
87
|
// field definition is absolute, subtracting struct offset gives bit offset inside struct
|
|
@@ -99,7 +103,7 @@ const createStructParser = ({
|
|
|
99
103
|
});
|
|
100
104
|
|
|
101
105
|
// eslint-disable-next-line immutable/no-mutation -- performance
|
|
102
|
-
result[field.name] = fieldParser.parse({ data: fieldData, offsetInBits: fieldOffsetInBits });
|
|
106
|
+
result[field.name/*!*/] = fieldParser.parse({ data: fieldData, offsetInBits: fieldOffsetInBits });
|
|
103
107
|
});
|
|
104
108
|
|
|
105
109
|
return result;
|
|
@@ -111,7 +115,11 @@ const createStructParser = ({
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
layoutedFields.forEach((field, idx) => {
|
|
114
|
-
|
|
118
|
+
if (field.pad) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const fieldValue = value[field.name/*!*/];
|
|
115
123
|
const fieldParser = fieldParsers[idx];
|
|
116
124
|
|
|
117
125
|
const offsetInBitsInByte = field.definition.offsetInBits % 8;
|
package/package.json
CHANGED