ya-struct 0.0.5 → 0.0.6
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/.editorconfig +8 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/npm-publish.yml +28 -0
- package/README.md +150 -18
- package/dist/lib/common.d.ts +14 -0
- package/dist/lib/common.js +30 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +21 -0
- package/dist/lib/layout.d.ts +48 -0
- package/dist/lib/layout.js +262 -0
- package/dist/lib/parser.d.ts +51 -0
- package/dist/lib/parser.js +87 -0
- package/dist/lib/types/array.d.ts +10 -0
- package/dist/lib/types/array.js +90 -0
- package/dist/lib/types/c-types.d.ts +13 -0
- package/dist/lib/types/c-types.js +222 -0
- package/dist/lib/types/index.d.ts +93 -0
- package/dist/lib/types/index.js +122 -0
- package/dist/lib/types/integer.d.ts +9 -0
- package/dist/lib/types/integer.js +160 -0
- package/dist/lib/types/pointer.d.ts +8 -0
- package/dist/lib/types/pointer.js +27 -0
- package/dist/lib/types/string.d.ts +6 -0
- package/dist/lib/types/string.js +56 -0
- package/dist/lib/types/struct.d.ts +11 -0
- package/dist/lib/types/struct.js +113 -0
- package/dist/lib/types/value.d.ts +12 -0
- package/dist/lib/types/value.js +8 -0
- package/eslint.config.js +127 -0
- package/lib/bit-buffer.ts +70 -0
- package/lib/common.ts +30 -0
- package/lib/index.ts +21 -0
- package/lib/layout.ts +262 -0
- package/lib/parser.ts +87 -0
- package/lib/types/array.ts +90 -0
- package/lib/types/c-types.ts +222 -0
- package/lib/types/index.ts +122 -0
- package/lib/types/integer.ts +160 -0
- package/lib/types/pointer.ts +27 -0
- package/lib/types/string.ts +56 -0
- package/lib/types/struct.ts +113 -0
- package/lib/types/value.ts +8 -0
- package/package.json +19 -15
- package/package.npm.json +25 -0
- package/samples/basic.ts +40 -0
- package/test/c-structs.ts +399 -0
- package/test/compile-util.ts +92 -0
- package/tsconfig.json +10 -0
- package/.eslintrc +0 -92
- package/.github/workflows/CI.yml +0 -39
- package/.prettierrc +0 -5
- package/lib/builder.js +0 -62
- package/lib/index.js +0 -159
- package/lib/marshaller.js +0 -88
- package/lib/refbuf.js +0 -11
- package/lib/types/basic.js +0 -200
- package/lib/types/ctypes.js +0 -160
- package/test/abi.js +0 -203
- package/test/basic.js +0 -92
- package/test/ctypes.js +0 -166
- package/test/ref.js +0 -35
package/.editorconfig
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
ci:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
name: Continous integration tests
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
- uses: actions/setup-node@v4
|
|
12
|
+
with:
|
|
13
|
+
node-version: '24'
|
|
14
|
+
- name: Install libc6-dev-i386
|
|
15
|
+
run: sudo apt-get update && sudo apt-get install -y libc6-dev-i386
|
|
16
|
+
- name: Install dependencies
|
|
17
|
+
run: npm ci
|
|
18
|
+
- name: Build
|
|
19
|
+
run: npm run build
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: npm run test
|
|
22
|
+
- name: Verify code with linter
|
|
23
|
+
run: npm run lint
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish Package to npmjs
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags:
|
|
5
|
+
- '*'
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write # Required for OIDC
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
id-token: write
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
# Setup .npmrc file to publish to npm
|
|
20
|
+
- uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '24'
|
|
23
|
+
registry-url: 'https://registry.npmjs.org'
|
|
24
|
+
- run: npm ci
|
|
25
|
+
- run: npm run build
|
|
26
|
+
- run: node node_modules/.bin/releasetool merge --local-package-json package.json --npm-package-json package.npm.json --output package.json
|
|
27
|
+
- run: node node_modules/.bin/releasetool patch-version --package-json package.json --package-version ${{ github.ref_name }}
|
|
28
|
+
- run: npm publish
|
package/README.md
CHANGED
|
@@ -1,24 +1,156 @@
|
|
|
1
1
|
# node-ya-struct
|
|
2
|
-
Yet Another Node.js Structure API
|
|
3
2
|
|
|
3
|
+
Yet Another Node.js Structure API - A TypeScript library for defining, parsing, and formatting binary data structures with precise control over memory layout, alignment, and ABI compatibility.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
This module allows you to parse and format fixed C-like data structures. Mainly this can be used for native code interoperability, network communication or file formats. The goal is to support types and alignments similar to C structures with support for endianness.
|
|
5
|
+
## Overview
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
`node-ya-struct` provides a type-safe way to work with binary data structures in Node.js. It handles complex memory layout calculations including padding, alignment, and platform-specific differences (data models, endianness, compilers). The library is particularly useful for:
|
|
8
|
+
|
|
9
|
+
- Interfacing with native C libraries and system APIs
|
|
10
|
+
- Parsing binary file formats
|
|
11
|
+
- Working with hardware interfaces and low-level protocols
|
|
12
|
+
- Cross-platform binary data serialization
|
|
10
13
|
|
|
11
14
|
## Features
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
|
|
16
|
+
- **Type-safe definitions** - Full TypeScript support with inferred types for parsed values
|
|
17
|
+
- **Multiple data models** - Support for LP64 (64-bit Unix) and ILP32 (32-bit) data models
|
|
18
|
+
- **Packed structs** - Support for packed structures without padding
|
|
19
|
+
- **Endianness control** - Little and big endian support
|
|
20
|
+
- **Zero dependencies** - Pure TypeScript implementation
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install ya-struct
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { define, types } from "ya-struct";
|
|
32
|
+
|
|
33
|
+
// Define a struct
|
|
34
|
+
const def = define({
|
|
35
|
+
definition: {
|
|
36
|
+
type: "struct",
|
|
37
|
+
packed: false,
|
|
38
|
+
fixedAbi: {},
|
|
39
|
+
fields: [
|
|
40
|
+
{ name: "a", definition: types.Int16 },
|
|
41
|
+
{ name: "b", definition: types.UInt16 },
|
|
42
|
+
{ name: "c", definition: types.UInt32 },
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create a parser with specific ABI settings
|
|
48
|
+
const parser = def.parser({
|
|
49
|
+
abi: {
|
|
50
|
+
endianness: "little",
|
|
51
|
+
dataModel: "LP64",
|
|
52
|
+
compiler: "gcc",
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Parse binary data
|
|
57
|
+
const value = {
|
|
58
|
+
a: 0n,
|
|
59
|
+
b: 1n,
|
|
60
|
+
c: 2n,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Format to binary
|
|
64
|
+
const buffer = parser.format({ value });
|
|
65
|
+
|
|
66
|
+
// Parse back
|
|
67
|
+
const parsed = parser.parse({ data: buffer });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API
|
|
71
|
+
|
|
72
|
+
### `define({ definition })`
|
|
73
|
+
|
|
74
|
+
Creates a struct definition that can be used to create parsers with specific ABI settings.
|
|
75
|
+
|
|
76
|
+
**Parameters:**
|
|
77
|
+
- `definition` - A struct definition object
|
|
78
|
+
|
|
79
|
+
**Returns:** An object with a `parser()` method
|
|
80
|
+
|
|
81
|
+
### `parser({ abi })`
|
|
82
|
+
|
|
83
|
+
Creates a parser for the defined structure with specific ABI settings.
|
|
84
|
+
|
|
85
|
+
**Parameters:**
|
|
86
|
+
- `abi.endianness` - `"little"` or `"big"`
|
|
87
|
+
- `abi.dataModel` - `"LP64"` (64-bit Unix/Linux) or `"ILP32"` (32-bit)
|
|
88
|
+
- `abi.compiler` - `"gcc"` (currently supported)
|
|
89
|
+
|
|
90
|
+
**Returns:** Parser object with:
|
|
91
|
+
- `size` - Size of the structure in bytes
|
|
92
|
+
- `parse({ data })` - Parse binary data to JavaScript values
|
|
93
|
+
- `format({ value })` - Format JavaScript values to binary data
|
|
94
|
+
- `layout` - Detailed memory layout information
|
|
95
|
+
|
|
96
|
+
### Built-in Types
|
|
97
|
+
|
|
98
|
+
Available via `types` export:
|
|
99
|
+
|
|
100
|
+
- **Integers:** `Int16`, `UInt16`, `Int32`, `UInt32`, `Int64`, `UInt64`
|
|
101
|
+
- **Strings:** `ascii({ length })` - Fixed-length null-terminated ASCII strings
|
|
102
|
+
- **Pointers:** `pointer` - Platform-specific pointer size
|
|
103
|
+
|
|
104
|
+
### Custom Types
|
|
105
|
+
|
|
106
|
+
You can define custom field types:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const definition = {
|
|
110
|
+
type: "struct",
|
|
111
|
+
packed: false,
|
|
112
|
+
fixedAbi: {},
|
|
113
|
+
fields: [
|
|
114
|
+
{
|
|
115
|
+
name: "myField",
|
|
116
|
+
definition: {
|
|
117
|
+
type: "integer",
|
|
118
|
+
sizeInBits: 32,
|
|
119
|
+
signed: true,
|
|
120
|
+
fixedAbi: {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
} as const;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Supported type kinds:
|
|
128
|
+
- `integer` - Custom integer with specified bit width
|
|
129
|
+
- `float` - Floating point numbers
|
|
130
|
+
- `pointer` - Pointer types
|
|
131
|
+
- `array` - Fixed-length arrays
|
|
132
|
+
- `struct` - Nested structures
|
|
133
|
+
- `string` - Fixed-length strings
|
|
134
|
+
- `c-type` - Native C types (char, int, long, etc.)
|
|
135
|
+
|
|
136
|
+
## Data Models
|
|
137
|
+
|
|
138
|
+
The library supports different C data models:
|
|
139
|
+
|
|
140
|
+
- **LP64** - Used on 64-bit Unix/Linux systems (long and pointer are 64-bit)
|
|
141
|
+
- **ILP32** - Used on 32-bit systems (int, long, and pointer are 32-bit)
|
|
142
|
+
|
|
143
|
+
## Packed Structures
|
|
144
|
+
|
|
145
|
+
Set `packed: true` in your struct definition to disable padding and alignment:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const def = define({
|
|
149
|
+
definition: {
|
|
150
|
+
type: "struct",
|
|
151
|
+
packed: true, // No padding between fields
|
|
152
|
+
fixedAbi: {},
|
|
153
|
+
fields: [...]
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type TDataModel = "LP64" | "ILP32";
|
|
2
|
+
type TCompiler = "gcc";
|
|
3
|
+
type TEndianness = "little" | "big";
|
|
4
|
+
type TAbi = {
|
|
5
|
+
endianness: TEndianness;
|
|
6
|
+
compiler: TCompiler;
|
|
7
|
+
dataModel: TDataModel;
|
|
8
|
+
};
|
|
9
|
+
declare const align: ({ offset, alignment }: {
|
|
10
|
+
offset: number;
|
|
11
|
+
alignment: number;
|
|
12
|
+
}) => number;
|
|
13
|
+
export type { TDataModel, TCompiler, TEndianness, TAbi };
|
|
14
|
+
export { align };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*type TDataModel = "LP64" | "ILP32";*/
|
|
2
|
+
/*type TCompiler = "gcc";*/
|
|
3
|
+
/*type TEndianness = "little" | "big";*/
|
|
4
|
+
|
|
5
|
+
/*type TAbi = {
|
|
6
|
+
endianness: TEndianness;
|
|
7
|
+
compiler: TCompiler;
|
|
8
|
+
dataModel: TDataModel;
|
|
9
|
+
};*/
|
|
10
|
+
|
|
11
|
+
const align = ({ offset, alignment }/*: { offset: number; alignment: number }*/)/*: number*/ => {
|
|
12
|
+
const remainder = offset % alignment;
|
|
13
|
+
if (remainder === 0) {
|
|
14
|
+
return offset;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return offset + (alignment - remainder);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/*export type {
|
|
21
|
+
TDataModel,
|
|
22
|
+
TCompiler,
|
|
23
|
+
TEndianness,
|
|
24
|
+
|
|
25
|
+
TAbi
|
|
26
|
+
};*/
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
align
|
|
30
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { define } from "./parser.js";
|
|
2
|
+
import { types } from "./types/index.js";
|
|
3
|
+
|
|
4
|
+
/*import type {
|
|
5
|
+
TAbi,
|
|
6
|
+
TCompiler,
|
|
7
|
+
TDataModel,
|
|
8
|
+
TEndianness,
|
|
9
|
+
} from "./common.ts";*/
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
define,
|
|
13
|
+
types
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/*export type {
|
|
17
|
+
TAbi,
|
|
18
|
+
TEndianness,
|
|
19
|
+
TCompiler,
|
|
20
|
+
TDataModel
|
|
21
|
+
};*/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type TAbi } from "./common.js";
|
|
2
|
+
import type { TFieldType } from "./types/index.js";
|
|
3
|
+
type TLayoutedField = {
|
|
4
|
+
readonly type: "integer";
|
|
5
|
+
readonly offsetInBits: number;
|
|
6
|
+
readonly sizeInBits: number;
|
|
7
|
+
readonly signed: boolean;
|
|
8
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
9
|
+
} | {
|
|
10
|
+
readonly type: "float";
|
|
11
|
+
readonly offsetInBits: number;
|
|
12
|
+
readonly sizeInBits: number;
|
|
13
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
14
|
+
} | {
|
|
15
|
+
readonly type: "pointer";
|
|
16
|
+
readonly offsetInBits: number;
|
|
17
|
+
readonly sizeInBits: number;
|
|
18
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
19
|
+
} | {
|
|
20
|
+
readonly type: "array";
|
|
21
|
+
readonly elementType: TLayoutedField;
|
|
22
|
+
readonly length: number;
|
|
23
|
+
readonly offsetInBits: number;
|
|
24
|
+
readonly sizeInBits: number;
|
|
25
|
+
} | {
|
|
26
|
+
readonly type: "struct";
|
|
27
|
+
readonly offsetInBits: number;
|
|
28
|
+
readonly sizeInBits: number;
|
|
29
|
+
readonly fields: readonly {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly definition: TLayoutedField;
|
|
32
|
+
}[];
|
|
33
|
+
readonly packed: boolean;
|
|
34
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
35
|
+
} | {
|
|
36
|
+
readonly type: "string";
|
|
37
|
+
readonly charSizeInBits: number;
|
|
38
|
+
readonly length: number;
|
|
39
|
+
readonly offsetInBits: number;
|
|
40
|
+
readonly sizeInBits: number;
|
|
41
|
+
};
|
|
42
|
+
declare const layout: ({ definition, abi, currentOffsetInBits }: {
|
|
43
|
+
definition: TFieldType;
|
|
44
|
+
abi: TAbi;
|
|
45
|
+
currentOffsetInBits: number;
|
|
46
|
+
}) => TLayoutedField;
|
|
47
|
+
export { layout };
|
|
48
|
+
export type { TLayoutedField };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { align, /*type TAbi */} from "./common.js";
|
|
2
|
+
import { createCTypeNormalizer } from "./types/c-types.js";
|
|
3
|
+
/*import type { TFieldType } from "./types/index.ts";*/
|
|
4
|
+
import nodeUtil from "node:util";
|
|
5
|
+
|
|
6
|
+
/*type TLayoutedField = {
|
|
7
|
+
readonly type: "integer";
|
|
8
|
+
readonly offsetInBits: number;
|
|
9
|
+
readonly sizeInBits: number;
|
|
10
|
+
readonly signed: boolean;
|
|
11
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
12
|
+
} | {
|
|
13
|
+
readonly type: "float";
|
|
14
|
+
readonly offsetInBits: number;
|
|
15
|
+
readonly sizeInBits: number;
|
|
16
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "pointer";
|
|
19
|
+
readonly offsetInBits: number;
|
|
20
|
+
readonly sizeInBits: number;
|
|
21
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
22
|
+
} | {
|
|
23
|
+
readonly type: "array";
|
|
24
|
+
readonly elementType: TLayoutedField;
|
|
25
|
+
readonly length: number;
|
|
26
|
+
readonly offsetInBits: number;
|
|
27
|
+
readonly sizeInBits: number;
|
|
28
|
+
} | {
|
|
29
|
+
readonly type: "struct";
|
|
30
|
+
readonly offsetInBits: number;
|
|
31
|
+
readonly sizeInBits: number;
|
|
32
|
+
readonly fields: readonly { readonly name: string; readonly definition: TLayoutedField }[];
|
|
33
|
+
readonly packed: boolean;
|
|
34
|
+
readonly fixedAbi: Partial<TAbi>;
|
|
35
|
+
} | {
|
|
36
|
+
readonly type: "string";
|
|
37
|
+
readonly charSizeInBits: number;
|
|
38
|
+
readonly length: number;
|
|
39
|
+
readonly offsetInBits: number;
|
|
40
|
+
readonly sizeInBits: number;
|
|
41
|
+
};*/
|
|
42
|
+
|
|
43
|
+
const pointerSizeInBitsByDataModel = ({ dataModel }/*: { dataModel: TAbi["dataModel"] }*/)/*: number*/ => {
|
|
44
|
+
switch (dataModel) {
|
|
45
|
+
case "LP64":
|
|
46
|
+
return 64;
|
|
47
|
+
case "ILP32":
|
|
48
|
+
return 32;
|
|
49
|
+
default:
|
|
50
|
+
throw Error(`unsupported data model "${dataModel}" for pointer size determination`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const layoutStruct = ({
|
|
55
|
+
definition,
|
|
56
|
+
abi,
|
|
57
|
+
currentOffsetInBits: initialOffsetInBits
|
|
58
|
+
}/*: {
|
|
59
|
+
definition: TFieldType & { type: "struct" },
|
|
60
|
+
abi: TAbi,
|
|
61
|
+
currentOffsetInBits: number
|
|
62
|
+
}*/)/*: TLayoutedField*/ => {
|
|
63
|
+
|
|
64
|
+
// TODO: implement alignment handling
|
|
65
|
+
const structAlignmentInBits = 64;
|
|
66
|
+
// const fieldAlignmentInBits = 64;
|
|
67
|
+
const fieldAlignmentInBits = 1;
|
|
68
|
+
const pointerSizeInBits = pointerSizeInBitsByDataModel({ dataModel: abi.dataModel });
|
|
69
|
+
|
|
70
|
+
let currentOffsetInBits = align({ offset: initialOffsetInBits, alignment: structAlignmentInBits });
|
|
71
|
+
let layoutedFields/*: (TLayoutedField & { type: "struct" })["fields"]*/ = [];
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line max-statements,complexity
|
|
74
|
+
definition.fields.forEach((field) => {
|
|
75
|
+
|
|
76
|
+
let normalizedField/*: TFieldType*/ = field.definition;
|
|
77
|
+
|
|
78
|
+
if (field.definition.type === "c-type") {
|
|
79
|
+
const cTypeNormalizer = createCTypeNormalizer({ abi });
|
|
80
|
+
normalizedField = cTypeNormalizer.normalize({ cField: field.definition });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!definition.packed) {
|
|
84
|
+
switch (normalizedField.type) {
|
|
85
|
+
case "integer":
|
|
86
|
+
case "float": {
|
|
87
|
+
const sizeInBits = normalizedField.sizeInBits;
|
|
88
|
+
|
|
89
|
+
if (abi.compiler === "gcc" && abi.dataModel === "ILP32" && sizeInBits === 64) {
|
|
90
|
+
// special handling for gcc 64-bit integers on ILP32 data model (alignment to 32 bits)
|
|
91
|
+
currentOffsetInBits = align({ offset: currentOffsetInBits, alignment: 32 });
|
|
92
|
+
} else {
|
|
93
|
+
currentOffsetInBits = align({ offset: currentOffsetInBits, alignment: sizeInBits });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "string": {
|
|
99
|
+
// no special alignment needed
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "pointer": {
|
|
103
|
+
currentOffsetInBits = align({ offset: currentOffsetInBits, alignment: pointerSizeInBits });
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
throw Error(`unsupported field type for struct layout: ${nodeUtil.inspect(field.definition)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// eslint-disable-next-line no-use-before-define
|
|
112
|
+
const fieldLayout = layout({ definition: normalizedField, abi, currentOffsetInBits });
|
|
113
|
+
|
|
114
|
+
layoutedFields = [
|
|
115
|
+
...layoutedFields,
|
|
116
|
+
{
|
|
117
|
+
name: field.name,
|
|
118
|
+
definition: fieldLayout
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
currentOffsetInBits = align({ offset: fieldLayout.offsetInBits + fieldLayout.sizeInBits, alignment: fieldAlignmentInBits });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
type: "struct",
|
|
127
|
+
offsetInBits: initialOffsetInBits,
|
|
128
|
+
sizeInBits: currentOffsetInBits - initialOffsetInBits,
|
|
129
|
+
fields: layoutedFields,
|
|
130
|
+
packed: definition.packed,
|
|
131
|
+
fixedAbi: definition.fixedAbi
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const layoutPrimitive = ({
|
|
136
|
+
definition,
|
|
137
|
+
abi,
|
|
138
|
+
currentOffsetInBits
|
|
139
|
+
}/*: {
|
|
140
|
+
definition: TFieldType & ({ type: "integer" } | { type: "float" } | { type: "pointer" }),
|
|
141
|
+
abi: TAbi,
|
|
142
|
+
currentOffsetInBits: number
|
|
143
|
+
}*/)/*: TLayoutedField*/ => {
|
|
144
|
+
|
|
145
|
+
if (definition.type === "integer") {
|
|
146
|
+
return {
|
|
147
|
+
type: "integer",
|
|
148
|
+
offsetInBits: currentOffsetInBits,
|
|
149
|
+
sizeInBits: definition.sizeInBits,
|
|
150
|
+
signed: definition.signed,
|
|
151
|
+
fixedAbi: definition.fixedAbi
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (definition.type === "float") {
|
|
156
|
+
return {
|
|
157
|
+
type: "float",
|
|
158
|
+
offsetInBits: currentOffsetInBits,
|
|
159
|
+
sizeInBits: definition.sizeInBits,
|
|
160
|
+
fixedAbi: definition.fixedAbi
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const pointerSizeInBits = pointerSizeInBitsByDataModel({ dataModel: abi.dataModel });
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
type: definition.type,
|
|
168
|
+
offsetInBits: currentOffsetInBits,
|
|
169
|
+
sizeInBits: pointerSizeInBits,
|
|
170
|
+
fixedAbi: definition.fixedAbi
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const layoutString = ({
|
|
175
|
+
definition,
|
|
176
|
+
currentOffsetInBits
|
|
177
|
+
}/*: {
|
|
178
|
+
definition: TFieldType & { type: "string" },
|
|
179
|
+
abi: TAbi,
|
|
180
|
+
currentOffsetInBits: number
|
|
181
|
+
}*/)/*: TLayoutedField*/ => {
|
|
182
|
+
|
|
183
|
+
const sizeInBits = definition.charSizeInBits * definition.length;
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
type: "string",
|
|
187
|
+
offsetInBits: currentOffsetInBits,
|
|
188
|
+
sizeInBits,
|
|
189
|
+
charSizeInBits: definition.charSizeInBits,
|
|
190
|
+
length: definition.length
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const layoutArray = ({
|
|
195
|
+
definition,
|
|
196
|
+
abi,
|
|
197
|
+
currentOffsetInBits
|
|
198
|
+
}/*: {
|
|
199
|
+
definition: TFieldType & { type: "array" },
|
|
200
|
+
abi: TAbi,
|
|
201
|
+
currentOffsetInBits: number
|
|
202
|
+
}*/)/*: TLayoutedField*/ => {
|
|
203
|
+
|
|
204
|
+
// eslint-disable-next-line no-use-before-define
|
|
205
|
+
const elementLayout = layout({ definition: definition.elementType, abi, currentOffsetInBits: 0 });
|
|
206
|
+
|
|
207
|
+
// TODO: handle alignment
|
|
208
|
+
const sizeInBits = elementLayout.sizeInBits * definition.length;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
type: "array",
|
|
212
|
+
offsetInBits: currentOffsetInBits,
|
|
213
|
+
sizeInBits,
|
|
214
|
+
elementType: elementLayout,
|
|
215
|
+
length: definition.length
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const layout = ({
|
|
220
|
+
definition,
|
|
221
|
+
abi,
|
|
222
|
+
currentOffsetInBits
|
|
223
|
+
}/*: {
|
|
224
|
+
definition: TFieldType,
|
|
225
|
+
abi: TAbi,
|
|
226
|
+
currentOffsetInBits: number
|
|
227
|
+
// eslint-disable-next-line complexity
|
|
228
|
+
}*/)/*: TLayoutedField*/ => {
|
|
229
|
+
|
|
230
|
+
if (definition.type === "struct") {
|
|
231
|
+
return layoutStruct({ definition, abi, currentOffsetInBits });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (definition.type === "integer" || definition.type === "float" || definition.type === "pointer") {
|
|
235
|
+
return layoutPrimitive({ definition, abi, currentOffsetInBits });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (definition.type === "string") {
|
|
239
|
+
return layoutString({ definition, abi, currentOffsetInBits });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (definition.type === "array") {
|
|
243
|
+
return layoutArray({ definition, abi, currentOffsetInBits });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (definition.type === "c-type") {
|
|
247
|
+
const cTypeNormalizer = createCTypeNormalizer({ abi });
|
|
248
|
+
const basicField = cTypeNormalizer.normalize({ cField: definition });
|
|
249
|
+
|
|
250
|
+
return layout({ definition: basicField, abi, currentOffsetInBits });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
throw Error("not implemented yet");
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export {
|
|
257
|
+
layout
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/*export type {
|
|
261
|
+
TLayoutedField
|
|
262
|
+
};*/
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TAbi } from "./common.js";
|
|
2
|
+
import { type TLayoutedField } from "./layout.js";
|
|
3
|
+
import type { TFieldType } from "./types/index.js";
|
|
4
|
+
type FieldValue<T extends TFieldType> = T extends {
|
|
5
|
+
type: "integer";
|
|
6
|
+
} ? bigint : T extends {
|
|
7
|
+
type: "float";
|
|
8
|
+
} ? number : T extends {
|
|
9
|
+
type: "pointer";
|
|
10
|
+
} ? bigint : T extends {
|
|
11
|
+
type: "string";
|
|
12
|
+
} ? string : T extends {
|
|
13
|
+
type: "array";
|
|
14
|
+
elementType: infer E;
|
|
15
|
+
length: number;
|
|
16
|
+
} ? FieldValue<E & TFieldType>[] : T extends {
|
|
17
|
+
type: "struct";
|
|
18
|
+
fields: infer F;
|
|
19
|
+
} ? StructValue<F & readonly {
|
|
20
|
+
name: string;
|
|
21
|
+
definition: TFieldType;
|
|
22
|
+
}[]> : never;
|
|
23
|
+
type StructValue<F extends readonly {
|
|
24
|
+
name: string;
|
|
25
|
+
definition: TFieldType;
|
|
26
|
+
}[]> = {
|
|
27
|
+
[K in F[number] as K["name"]]: FieldValue<K["definition"]>;
|
|
28
|
+
};
|
|
29
|
+
type Simplify<T> = {
|
|
30
|
+
[K in keyof T]: T[K];
|
|
31
|
+
} & {};
|
|
32
|
+
type TParsedValueOfDefinition<T extends TFieldType> = Simplify<FieldValue<T>>;
|
|
33
|
+
type TParser<T extends TFieldType> = {
|
|
34
|
+
size: number;
|
|
35
|
+
parse: ({ data }: {
|
|
36
|
+
data: Uint8Array;
|
|
37
|
+
}) => TParsedValueOfDefinition<T>;
|
|
38
|
+
format: ({ value }: {
|
|
39
|
+
value: TParsedValueOfDefinition<T>;
|
|
40
|
+
}) => Uint8Array;
|
|
41
|
+
layout: TLayoutedField;
|
|
42
|
+
};
|
|
43
|
+
declare const define: <const T extends TFieldType>({ definition }: {
|
|
44
|
+
definition: T;
|
|
45
|
+
}) => {
|
|
46
|
+
definition: T;
|
|
47
|
+
parser: ({ abi }: {
|
|
48
|
+
abi: TAbi;
|
|
49
|
+
}) => TParser<T>;
|
|
50
|
+
};
|
|
51
|
+
export { define };
|