@fulcro/types 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -15
- package/README.md +86 -66
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/struct/arithmetic.d.ts +131 -0
- package/dist/struct/arithmetic.js +20 -0
- package/dist/struct/codec.d.ts +58 -0
- package/dist/struct/codec.js +325 -0
- package/dist/struct/index.d.ts +206 -0
- package/dist/struct/index.js +267 -0
- package/package.json +71 -71
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codecOf = exports.registerStructCodec = void 0;
|
|
4
|
+
const decimal_1 = require("../decimal/index.js");
|
|
5
|
+
const format_1 = require("../decimal/format.js");
|
|
6
|
+
const parse_1 = require("../decimal/parse.js");
|
|
7
|
+
const parts_1 = require("../decimal/parts.js");
|
|
8
|
+
const doublePrecisionFloat_1 = require("../doublePrecisionFloat/index.js");
|
|
9
|
+
const halfPrecisionFloat_1 = require("../halfPrecisionFloat/index.js");
|
|
10
|
+
const signedInteger_1 = require("../signedInteger/index.js");
|
|
11
|
+
const singlePrecisionFloat_1 = require("../singlePrecisionFloat/index.js");
|
|
12
|
+
const unsignedInteger_1 = require("../unsignedInteger/index.js");
|
|
13
|
+
/** Bits of a 64-bit half of a 128-bit value. */
|
|
14
|
+
const HALF_WIDTH = 64n;
|
|
15
|
+
/**
|
|
16
|
+
* The codec of a 128-bit integer, as two 64-bit halves, low half first.
|
|
17
|
+
*
|
|
18
|
+
* @param descriptor Descriptor of the width.
|
|
19
|
+
* @returns The codec.
|
|
20
|
+
*/
|
|
21
|
+
const integer128Codec = (descriptor) => ({
|
|
22
|
+
size: 16,
|
|
23
|
+
alignment: 16,
|
|
24
|
+
read: (view, offset) => {
|
|
25
|
+
const low = view.getBigUint64(offset, true);
|
|
26
|
+
const high = descriptor.signed
|
|
27
|
+
? view.getBigInt64(offset + 8, true)
|
|
28
|
+
: view.getBigUint64(offset + 8, true);
|
|
29
|
+
return (high << HALF_WIDTH) | low;
|
|
30
|
+
},
|
|
31
|
+
write: (view, offset, value) => {
|
|
32
|
+
view.setBigUint64(offset, BigInt.asUintN(64, value), true);
|
|
33
|
+
view.setBigUint64(offset + 8, BigInt.asUintN(64, value >> HALF_WIDTH), true);
|
|
34
|
+
},
|
|
35
|
+
equals: (left, right) => descriptor.equals(left, right),
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* The codec of a fixed-width integer, through the `DataView` accessor of its
|
|
39
|
+
* width, or two of them at 128 bits.
|
|
40
|
+
*
|
|
41
|
+
* @param descriptor Descriptor of the width.
|
|
42
|
+
* @returns The codec.
|
|
43
|
+
*/
|
|
44
|
+
const integerCodec = (descriptor) => {
|
|
45
|
+
const { signed, width } = descriptor;
|
|
46
|
+
if (width === 128)
|
|
47
|
+
return integer128Codec(descriptor);
|
|
48
|
+
const size = width / 8;
|
|
49
|
+
const equals = (left, right) => descriptor.equals(left, right);
|
|
50
|
+
switch (width) {
|
|
51
|
+
case 8:
|
|
52
|
+
return {
|
|
53
|
+
size,
|
|
54
|
+
alignment: size,
|
|
55
|
+
read: (view, offset) => signed ? view.getInt8(offset) : view.getUint8(offset),
|
|
56
|
+
write: (view, offset, value) => signed
|
|
57
|
+
? view.setInt8(offset, value)
|
|
58
|
+
: view.setUint8(offset, value),
|
|
59
|
+
equals,
|
|
60
|
+
};
|
|
61
|
+
case 16:
|
|
62
|
+
return {
|
|
63
|
+
size,
|
|
64
|
+
alignment: size,
|
|
65
|
+
read: (view, offset) => signed ? view.getInt16(offset, true) : view.getUint16(offset, true),
|
|
66
|
+
write: (view, offset, value) => signed
|
|
67
|
+
? view.setInt16(offset, value, true)
|
|
68
|
+
: view.setUint16(offset, value, true),
|
|
69
|
+
equals,
|
|
70
|
+
};
|
|
71
|
+
case 32:
|
|
72
|
+
return {
|
|
73
|
+
size,
|
|
74
|
+
alignment: size,
|
|
75
|
+
read: (view, offset) => signed ? view.getInt32(offset, true) : view.getUint32(offset, true),
|
|
76
|
+
write: (view, offset, value) => signed
|
|
77
|
+
? view.setInt32(offset, value, true)
|
|
78
|
+
: view.setUint32(offset, value, true),
|
|
79
|
+
equals,
|
|
80
|
+
};
|
|
81
|
+
default:
|
|
82
|
+
return {
|
|
83
|
+
size,
|
|
84
|
+
alignment: size,
|
|
85
|
+
read: (view, offset) => signed
|
|
86
|
+
? view.getBigInt64(offset, true)
|
|
87
|
+
: view.getBigUint64(offset, true),
|
|
88
|
+
write: (view, offset, value) => signed
|
|
89
|
+
? view.setBigInt64(offset, value, true)
|
|
90
|
+
: view.setBigUint64(offset, value, true),
|
|
91
|
+
equals,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/** Exponent of the smallest normal half precision value. */
|
|
96
|
+
const HALF_MINIMUM_NORMAL_EXPONENT = -14;
|
|
97
|
+
/** Spacing of the half precision subnormals, 2^-24. */
|
|
98
|
+
const HALF_SUBNORMAL_SPACING = 2 ** -24;
|
|
99
|
+
/**
|
|
100
|
+
* The binary16 bit pattern of a half precision value.
|
|
101
|
+
*
|
|
102
|
+
* Written here rather than delegated to `DataView.prototype.setFloat16`, which
|
|
103
|
+
* Node 22 — the oldest line this package supports — does not have. The value
|
|
104
|
+
* is already exactly representable, so every division below is exact and
|
|
105
|
+
* nothing is rounded: this only rearranges bits.
|
|
106
|
+
*
|
|
107
|
+
* @param value A half precision value.
|
|
108
|
+
* @returns Its sixteen bits.
|
|
109
|
+
*/
|
|
110
|
+
const encodeHalf = (value) => {
|
|
111
|
+
if (Number.isNaN(value))
|
|
112
|
+
return 0x7e00;
|
|
113
|
+
const sign = value < 0 || Object.is(value, -0) ? 0x8000 : 0;
|
|
114
|
+
const magnitude = Math.abs(value);
|
|
115
|
+
if (magnitude === Infinity)
|
|
116
|
+
return sign | 0x7c00;
|
|
117
|
+
if (magnitude < 2 ** HALF_MINIMUM_NORMAL_EXPONENT) {
|
|
118
|
+
return sign | (magnitude / HALF_SUBNORMAL_SPACING);
|
|
119
|
+
}
|
|
120
|
+
// `Math.log2` is not exact next to a power of two, so the estimate is
|
|
121
|
+
// corrected against the power itself.
|
|
122
|
+
let exponent = Math.floor(Math.log2(magnitude));
|
|
123
|
+
if (2 ** exponent > magnitude)
|
|
124
|
+
exponent--;
|
|
125
|
+
else if (2 ** (exponent + 1) <= magnitude)
|
|
126
|
+
exponent++;
|
|
127
|
+
return (sign | ((exponent + 15) << 10) | ((magnitude / 2 ** exponent - 1) * 1024));
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* The half precision value of a binary16 bit pattern.
|
|
131
|
+
*
|
|
132
|
+
* @param bits Sixteen bits.
|
|
133
|
+
* @returns The value they encode.
|
|
134
|
+
*/
|
|
135
|
+
const decodeHalf = (bits) => {
|
|
136
|
+
const sign = bits & 0x8000 ? -1 : 1;
|
|
137
|
+
const exponent = (bits >> 10) & 0x1f;
|
|
138
|
+
const fraction = bits & 0x3ff;
|
|
139
|
+
if (exponent === 0)
|
|
140
|
+
return sign * fraction * HALF_SUBNORMAL_SPACING;
|
|
141
|
+
if (exponent === 0x1f)
|
|
142
|
+
return fraction === 0 ? sign * Infinity : NaN;
|
|
143
|
+
return sign * (1 + fraction / 1024) * 2 ** (exponent - 15);
|
|
144
|
+
};
|
|
145
|
+
/** The codecs of the three binary float formats, by descriptor. */
|
|
146
|
+
const floatCodecs = new Map([
|
|
147
|
+
[
|
|
148
|
+
halfPrecisionFloat_1.HalfPrecisionFloat,
|
|
149
|
+
{
|
|
150
|
+
size: 2,
|
|
151
|
+
alignment: 2,
|
|
152
|
+
read: (view, offset) => decodeHalf(view.getUint16(offset, true)),
|
|
153
|
+
write: (view, offset, value) => view.setUint16(offset, encodeHalf(value), true),
|
|
154
|
+
equals: (left, right) => halfPrecisionFloat_1.HalfPrecisionFloat.equals(left, right),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
[
|
|
158
|
+
singlePrecisionFloat_1.SinglePrecisionFloat,
|
|
159
|
+
{
|
|
160
|
+
size: 4,
|
|
161
|
+
alignment: 4,
|
|
162
|
+
read: (view, offset) => view.getFloat32(offset, true),
|
|
163
|
+
write: (view, offset, value) => view.setFloat32(offset, value, true),
|
|
164
|
+
equals: (left, right) => singlePrecisionFloat_1.SinglePrecisionFloat.equals(left, right),
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
[
|
|
168
|
+
doublePrecisionFloat_1.DoublePrecisionFloat,
|
|
169
|
+
{
|
|
170
|
+
size: 8,
|
|
171
|
+
alignment: 8,
|
|
172
|
+
read: (view, offset) => view.getFloat64(offset, true),
|
|
173
|
+
write: (view, offset, value) => view.setFloat64(offset, value, true),
|
|
174
|
+
equals: (left, right) => doublePrecisionFloat_1.DoublePrecisionFloat.equals(left, right),
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
]);
|
|
178
|
+
/** Bias of the decimal128 exponent: the stored field is `exponent + 6176`. */
|
|
179
|
+
const DECIMAL_EXPONENT_BIAS = 6176;
|
|
180
|
+
/**
|
|
181
|
+
* Largest exponent a decimal128 coefficient can be scaled by: the largest
|
|
182
|
+
* value, thirty-four nines, has its last digit at 10^6111.
|
|
183
|
+
*/
|
|
184
|
+
const DECIMAL_MAXIMUM_EXPONENT = 6111;
|
|
185
|
+
/** Bits of the decimal128 coefficient field. */
|
|
186
|
+
const DECIMAL_COEFFICIENT_BITS = 113n;
|
|
187
|
+
/** The coefficients a decimal128 holds are below this; above it is non-canonical. */
|
|
188
|
+
const DECIMAL_COEFFICIENT_LIMIT = (0, parts_1.powerOfTen)(34);
|
|
189
|
+
/**
|
|
190
|
+
* The decimal128 bits of a decimal, in the binary integer decimal encoding
|
|
191
|
+
* IEEE 754-2008 defines: a sign bit, fourteen bits of biased exponent and a
|
|
192
|
+
* 113-bit binary coefficient.
|
|
193
|
+
*
|
|
194
|
+
* The parts are normalised, which can leave a large value with a short
|
|
195
|
+
* coefficient and an exponent past the one the format stores — 1 × 10^6144.
|
|
196
|
+
* Those are scaled back into range by moving digits into the coefficient,
|
|
197
|
+
* which always has room for them because the value is within range.
|
|
198
|
+
*
|
|
199
|
+
* @param parts The value.
|
|
200
|
+
* @returns Its 128 bits.
|
|
201
|
+
*/
|
|
202
|
+
const encodeDecimal = (parts) => {
|
|
203
|
+
const sign = parts.negative ? 1n << 127n : 0n;
|
|
204
|
+
if (parts.kind === 'nan')
|
|
205
|
+
return 0x7c00000000000000n << HALF_WIDTH;
|
|
206
|
+
if (parts.kind === 'infinity') {
|
|
207
|
+
return sign | (0x7800000000000000n << HALF_WIDTH);
|
|
208
|
+
}
|
|
209
|
+
let { coefficient, exponent } = parts;
|
|
210
|
+
if (exponent > DECIMAL_MAXIMUM_EXPONENT) {
|
|
211
|
+
coefficient *= (0, parts_1.powerOfTen)(exponent - DECIMAL_MAXIMUM_EXPONENT);
|
|
212
|
+
exponent = DECIMAL_MAXIMUM_EXPONENT;
|
|
213
|
+
}
|
|
214
|
+
return (sign |
|
|
215
|
+
(BigInt(exponent + DECIMAL_EXPONENT_BIAS) << DECIMAL_COEFFICIENT_BITS) |
|
|
216
|
+
coefficient);
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* The decimal of decimal128 bits.
|
|
220
|
+
*
|
|
221
|
+
* A coefficient past thirty-four digits — including every one written in the
|
|
222
|
+
* encoding's second form, whose leading bits are `11` — is non-canonical, and
|
|
223
|
+
* IEEE 754 reads it as zero. Trailing zeros are moved into the exponent, which
|
|
224
|
+
* is the normalised form every `Decimal` holds.
|
|
225
|
+
*
|
|
226
|
+
* @param bits 128 bits.
|
|
227
|
+
* @returns The parts they encode.
|
|
228
|
+
*/
|
|
229
|
+
const decodeDecimal = (bits) => {
|
|
230
|
+
const negative = bits >> 127n === 1n;
|
|
231
|
+
const combination = (bits >> 122n) & 0x1fn;
|
|
232
|
+
if (combination === 0x1fn) {
|
|
233
|
+
return { kind: 'nan', negative: false, coefficient: 0n, exponent: 0 };
|
|
234
|
+
}
|
|
235
|
+
if (combination === 0x1en) {
|
|
236
|
+
return { kind: 'infinity', negative, coefficient: 0n, exponent: 0 };
|
|
237
|
+
}
|
|
238
|
+
let coefficient = ((bits >> 125n) & 0x3n) === 0x3n
|
|
239
|
+
? 0n
|
|
240
|
+
: bits & ((1n << DECIMAL_COEFFICIENT_BITS) - 1n);
|
|
241
|
+
if (coefficient >= DECIMAL_COEFFICIENT_LIMIT)
|
|
242
|
+
coefficient = 0n;
|
|
243
|
+
if (coefficient === 0n) {
|
|
244
|
+
return { kind: 'finite', negative, coefficient: 0n, exponent: 0 };
|
|
245
|
+
}
|
|
246
|
+
let exponent = Number((bits >> DECIMAL_COEFFICIENT_BITS) & 0x3fffn) -
|
|
247
|
+
DECIMAL_EXPONENT_BIAS;
|
|
248
|
+
while (coefficient % 10n === 0n) {
|
|
249
|
+
coefficient /= 10n;
|
|
250
|
+
exponent++;
|
|
251
|
+
}
|
|
252
|
+
return { kind: 'finite', negative, coefficient, exponent };
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* The codec of `Decimal`, as decimal128.
|
|
256
|
+
*
|
|
257
|
+
* Its parts are private to the class, so they travel through its text, which
|
|
258
|
+
* reads back exactly: the shortest text of a normalised value names exactly
|
|
259
|
+
* that value.
|
|
260
|
+
*/
|
|
261
|
+
const decimalCodec = {
|
|
262
|
+
size: 16,
|
|
263
|
+
alignment: 16,
|
|
264
|
+
read: (view, offset) => {
|
|
265
|
+
const bits = (view.getBigUint64(offset + 8, true) << HALF_WIDTH) |
|
|
266
|
+
view.getBigUint64(offset, true);
|
|
267
|
+
return decimal_1.Decimal.from((0, format_1.formatDecimal)(decodeDecimal(bits)));
|
|
268
|
+
},
|
|
269
|
+
write: (view, offset, value) => {
|
|
270
|
+
const bits = encodeDecimal((0, parse_1.parseDecimal)(String(value)));
|
|
271
|
+
view.setBigUint64(offset, BigInt.asUintN(64, bits), true);
|
|
272
|
+
view.setBigUint64(offset + 8, bits >> HALF_WIDTH, true);
|
|
273
|
+
},
|
|
274
|
+
equals: (left, right) => left.equals(right),
|
|
275
|
+
};
|
|
276
|
+
/** Codecs registered by the structs themselves, so one can nest in another. */
|
|
277
|
+
const structCodecs = new WeakMap();
|
|
278
|
+
/** Widths a fixed-width integer comes in. */
|
|
279
|
+
const INTEGER_WIDTHS = [8, 16, 32, 64, 128];
|
|
280
|
+
/**
|
|
281
|
+
* Tells whether a value is the descriptor `SignedInteger` or `UnsignedInteger`
|
|
282
|
+
* returns for its width — the same object, not one shaped like it.
|
|
283
|
+
*
|
|
284
|
+
* @param descriptor Value to inspect.
|
|
285
|
+
* @returns `true` for an integer descriptor of this package.
|
|
286
|
+
*/
|
|
287
|
+
const isIntegerDescriptor = (descriptor) => {
|
|
288
|
+
const { signed, width } = descriptor;
|
|
289
|
+
if (typeof signed !== 'boolean' || !INTEGER_WIDTHS.includes(width)) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const factory = signed ? signedInteger_1.SignedInteger : unsignedInteger_1.UnsignedInteger;
|
|
293
|
+
return factory(width) === descriptor;
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Registers the codec of a struct, so that another struct can hold it as a
|
|
297
|
+
* field.
|
|
298
|
+
*
|
|
299
|
+
* @param descriptor Descriptor of the struct.
|
|
300
|
+
* @param codec How it is stored.
|
|
301
|
+
*/
|
|
302
|
+
const registerStructCodec = (descriptor, codec) => {
|
|
303
|
+
structCodecs.set(descriptor, codec);
|
|
304
|
+
};
|
|
305
|
+
exports.registerStructCodec = registerStructCodec;
|
|
306
|
+
/**
|
|
307
|
+
* The codec of a field's descriptor.
|
|
308
|
+
*
|
|
309
|
+
* @param descriptor What the field was declared with.
|
|
310
|
+
* @returns Its codec, or `undefined` for a descriptor with no fixed layout.
|
|
311
|
+
*/
|
|
312
|
+
const codecOf = (descriptor) => {
|
|
313
|
+
if (typeof descriptor !== 'object' && typeof descriptor !== 'function') {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
if (descriptor === null)
|
|
317
|
+
return undefined;
|
|
318
|
+
if (descriptor === decimal_1.Decimal)
|
|
319
|
+
return decimalCodec;
|
|
320
|
+
const known = floatCodecs.get(descriptor) ?? structCodecs.get(descriptor);
|
|
321
|
+
if (known !== undefined)
|
|
322
|
+
return known;
|
|
323
|
+
return isIntegerDescriptor(descriptor) ? integerCodec(descriptor) : undefined;
|
|
324
|
+
};
|
|
325
|
+
exports.codecOf = codecOf;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { Layout } from '../layout/index.js';
|
|
2
|
+
import type { Add, LargestAlignment, RoundUp } from './arithmetic.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a field of a struct may be declared with: the descriptor of a type that
|
|
5
|
+
* declares a fixed layout — a numeric type of this package other than
|
|
6
|
+
* `BigInteger`, or another struct.
|
|
7
|
+
*/
|
|
8
|
+
interface FieldDescriptor {
|
|
9
|
+
is(value: unknown): value is Layout<number, number>;
|
|
10
|
+
}
|
|
11
|
+
/** The fields of a struct, by name, in declaration order. */
|
|
12
|
+
type StructFields = {
|
|
13
|
+
readonly [field: string]: FieldDescriptor;
|
|
14
|
+
};
|
|
15
|
+
/** The methods of a struct, by name. */
|
|
16
|
+
type StructMethods = {
|
|
17
|
+
readonly [method: string]: (...parameters: never[]) => unknown;
|
|
18
|
+
};
|
|
19
|
+
/** What a struct declared without methods has: none. */
|
|
20
|
+
type NoMethods = Record<never, never>;
|
|
21
|
+
/** The type of the values a descriptor recognises. */
|
|
22
|
+
type ValueOf<TDescriptor> = TDescriptor extends {
|
|
23
|
+
is(value: unknown): value is infer T;
|
|
24
|
+
} ? T : never;
|
|
25
|
+
/** What a descriptor's `from` accepts. */
|
|
26
|
+
type SourceOf<TDescriptor> = TDescriptor extends {
|
|
27
|
+
from(value: infer TSource): unknown;
|
|
28
|
+
} ? TSource : never;
|
|
29
|
+
/** The layout a field's type declares. */
|
|
30
|
+
type FieldLayout<TDescriptor> = ValueOf<TDescriptor> extends Layout<infer TSize, infer TAlignment> ? {
|
|
31
|
+
readonly size: TSize;
|
|
32
|
+
readonly alignment: TAlignment;
|
|
33
|
+
} : never;
|
|
34
|
+
/** A union, as a tuple in no particular order — enough to sum over it. */
|
|
35
|
+
type UnionToTuple<TUnion, TTuple extends unknown[] = []> = [TUnion] extends [
|
|
36
|
+
never
|
|
37
|
+
] ? TTuple : UnionToTuple<Exclude<TUnion, LastOf<TUnion>>, [LastOf<TUnion>, ...TTuple]>;
|
|
38
|
+
/** One member of a union, picked by how TypeScript orders overloads. */
|
|
39
|
+
type LastOf<TUnion> = (TUnion extends unknown ? (member: () => TUnion) => void : never) extends (member: infer TIntersection) => void ? TIntersection extends () => infer TMember ? TMember : never : never;
|
|
40
|
+
/** The sizes of some fields, added up. */
|
|
41
|
+
type SumOfSizes<TFields extends StructFields, TKeys extends unknown[], TSum extends number = 0> = TKeys extends [infer TKey extends keyof TFields, ...infer TRest] ? SumOfSizes<TFields, TRest, Add<TSum, FieldLayout<TFields[TKey]>['size']>> : TSum;
|
|
42
|
+
/** Alignment of a struct: the largest alignment of its fields. */
|
|
43
|
+
type StructAlignment<TFields extends StructFields> = LargestAlignment<{
|
|
44
|
+
[TKey in keyof TFields]: FieldLayout<TFields[TKey]>['alignment'];
|
|
45
|
+
}[keyof TFields]>;
|
|
46
|
+
/**
|
|
47
|
+
* Size of a struct: its fields, which pack without padding once ordered by
|
|
48
|
+
* alignment, rounded up to its alignment. Independent of the order the fields
|
|
49
|
+
* were declared in, which is why it can be computed here at all — a type does
|
|
50
|
+
* not promise an order for its keys.
|
|
51
|
+
*/
|
|
52
|
+
type StructSize<TFields extends StructFields> = RoundUp<SumOfSizes<TFields, UnionToTuple<keyof TFields>>, StructAlignment<TFields>>;
|
|
53
|
+
/** The fields of a value of a struct, and its layout. */
|
|
54
|
+
type StructData<TFields extends StructFields> = {
|
|
55
|
+
readonly [TKey in keyof TFields]: ValueOf<TFields[TKey]>;
|
|
56
|
+
} & Layout<StructSize<TFields>, StructAlignment<TFields>>;
|
|
57
|
+
/**
|
|
58
|
+
* A value of a struct: its fields, and its methods when it declares any. A
|
|
59
|
+
* struct without methods is its fields alone, exactly as before methods
|
|
60
|
+
* existed, so nothing it inferred changes.
|
|
61
|
+
*/
|
|
62
|
+
type StructValue<TFields extends StructFields, TMethods extends StructMethods = NoMethods> = [keyof TMethods] extends [never] ? StructData<TFields> : StructData<TFields> & Readonly<TMethods>;
|
|
63
|
+
/** What a struct's `from` accepts: each field in what its own `from` accepts. */
|
|
64
|
+
type StructSource<TFields extends StructFields> = {
|
|
65
|
+
readonly [TKey in keyof TFields]: SourceOf<TFields[TKey]>;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The descriptor of a struct: its layout, and how its values are made,
|
|
69
|
+
* recognised, compared and stored.
|
|
70
|
+
*
|
|
71
|
+
* @template TFields Descriptors of the fields, by name.
|
|
72
|
+
* @template TMethods Methods every value carries, by name; none by default.
|
|
73
|
+
*/
|
|
74
|
+
export interface StructType<TFields extends StructFields, TMethods extends StructMethods = NoMethods> {
|
|
75
|
+
/** Name of the struct, as it reads in an error message. */
|
|
76
|
+
readonly name: string;
|
|
77
|
+
/**
|
|
78
|
+
* Where each field sits and how much room the struct takes.
|
|
79
|
+
*
|
|
80
|
+
* Fields are placed by alignment, largest first, and in declaration order
|
|
81
|
+
* among equals — so no field needs padding before it, and only the end of
|
|
82
|
+
* the struct is padded, up to its alignment, so that the next one in an
|
|
83
|
+
* array starts aligned. `size` and `alignment` are the numbers
|
|
84
|
+
* `sizeOf<T>()` and `alignOf<T>()` report.
|
|
85
|
+
*/
|
|
86
|
+
readonly layout: {
|
|
87
|
+
readonly size: StructSize<TFields>;
|
|
88
|
+
readonly alignment: StructAlignment<TFields>;
|
|
89
|
+
readonly fields: {
|
|
90
|
+
readonly [TKey in keyof TFields]: {
|
|
91
|
+
readonly offset: number;
|
|
92
|
+
readonly size: FieldLayout<TFields[TKey]>['size'];
|
|
93
|
+
readonly alignment: FieldLayout<TFields[TKey]>['alignment'];
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Makes a value, converting each field with its own type's `from`.
|
|
99
|
+
*
|
|
100
|
+
* @param source One entry per field, and nothing else.
|
|
101
|
+
* @returns The value, frozen, carrying the struct's methods.
|
|
102
|
+
* @throws {TypeError} When a field is missing or not a field of the struct.
|
|
103
|
+
* @throws {RangeError} When a field's own conversion refuses its value; the
|
|
104
|
+
* message names the field, and `cause` is the original error.
|
|
105
|
+
*/
|
|
106
|
+
from(source: StructSource<TFields>): StructValue<TFields, TMethods>;
|
|
107
|
+
/**
|
|
108
|
+
* Tells whether a value is one of this struct's: frozen, with exactly its
|
|
109
|
+
* fields, each of its type — and, when the struct declares methods, made by
|
|
110
|
+
* this struct, since an object with the right fields alone would not carry
|
|
111
|
+
* them.
|
|
112
|
+
*
|
|
113
|
+
* @param value Value to inspect.
|
|
114
|
+
* @returns `true` when it is.
|
|
115
|
+
*/
|
|
116
|
+
is(value: unknown): value is StructValue<TFields, TMethods>;
|
|
117
|
+
/**
|
|
118
|
+
* Compares two values field by field, each as its own type compares — so a
|
|
119
|
+
* `NaN` field makes a value unequal to itself, as it does in C#.
|
|
120
|
+
*
|
|
121
|
+
* @param left First value.
|
|
122
|
+
* @param right Second value.
|
|
123
|
+
* @returns `true` when every field is equal.
|
|
124
|
+
*/
|
|
125
|
+
equals(left: StructValue<TFields, TMethods>, right: StructValue<TFields, TMethods>): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Reads a value from bytes, in the layout of {@link StructType.layout},
|
|
128
|
+
* little-endian.
|
|
129
|
+
*
|
|
130
|
+
* @param view Bytes to read from.
|
|
131
|
+
* @param offset Where the value starts.
|
|
132
|
+
* @returns The value, frozen, carrying the struct's methods.
|
|
133
|
+
* @throws {RangeError} When the struct does not fit in the view at that
|
|
134
|
+
* offset.
|
|
135
|
+
*/
|
|
136
|
+
read(view: DataView, offset: number): StructValue<TFields, TMethods>;
|
|
137
|
+
/**
|
|
138
|
+
* Writes a value into bytes, in the layout of {@link StructType.layout},
|
|
139
|
+
* little-endian. Padding bytes are left as they were.
|
|
140
|
+
*
|
|
141
|
+
* @param view Bytes to write into.
|
|
142
|
+
* @param offset Where the value starts.
|
|
143
|
+
* @param value Value to write.
|
|
144
|
+
* @throws {RangeError} When the struct does not fit in the view at that
|
|
145
|
+
* offset.
|
|
146
|
+
*/
|
|
147
|
+
write(view: DataView, offset: number, value: StructValue<TFields, TMethods>): void;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The type of the values of a struct, named from its descriptor.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* export const Vector3 = struct('Vector3', { x: SinglePrecisionFloat, … });
|
|
154
|
+
* export type Vector3 = Struct<typeof Vector3>;
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @template TDescriptor Type of the descriptor `struct` returned.
|
|
158
|
+
*/
|
|
159
|
+
export type Struct<TDescriptor extends StructType<StructFields, StructMethods>> = TDescriptor extends StructType<infer TFields, infer TMethods> ? StructValue<TFields, TMethods> : never;
|
|
160
|
+
/**
|
|
161
|
+
* Declares a struct: a value type with a fixed layout.
|
|
162
|
+
*
|
|
163
|
+
* ```ts
|
|
164
|
+
* export const Vector3 = struct('Vector3', {
|
|
165
|
+
* x: SinglePrecisionFloat,
|
|
166
|
+
* y: SinglePrecisionFloat,
|
|
167
|
+
* z: SinglePrecisionFloat,
|
|
168
|
+
* });
|
|
169
|
+
* export type Vector3 = Struct<typeof Vector3>;
|
|
170
|
+
*
|
|
171
|
+
* const up: Vector3 = Vector3.from({ x: 0, y: 1, z: 0 });
|
|
172
|
+
*
|
|
173
|
+
* Vector3.layout.size; // 12
|
|
174
|
+
* Vector3.write(new DataView(buffer), 0, up);
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* A value has no identity: it is frozen, two values with the same fields are
|
|
178
|
+
* equal by {@link StructType.equals}, and it can be written into bytes and
|
|
179
|
+
* read back as the same value. It is still a JavaScript object while it is
|
|
180
|
+
* held as one; the layout is what it occupies when it is stored.
|
|
181
|
+
*
|
|
182
|
+
* Methods, when given, are shared by every value through one prototype: they
|
|
183
|
+
* take no bytes and are not fields, so the layout, `equals` and the bytes are
|
|
184
|
+
* the same as without them. `this` is the value, which is frozen — a method
|
|
185
|
+
* that changes something returns a new value:
|
|
186
|
+
*
|
|
187
|
+
* ```ts
|
|
188
|
+
* const Vector3 = struct('Vector3', { x: SinglePrecisionFloat, … }, {
|
|
189
|
+
* length() {
|
|
190
|
+
* return Math.hypot(this.x, this.y, this.z);
|
|
191
|
+
* },
|
|
192
|
+
* });
|
|
193
|
+
*
|
|
194
|
+
* Vector3.from({ x: 3, y: 4, z: 0 }).length(); // 5
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* @param name Name of the struct, for error messages.
|
|
198
|
+
* @param fields Descriptor of each field, by name.
|
|
199
|
+
* @param methods Function of each method, by name.
|
|
200
|
+
* @returns The descriptor of the struct.
|
|
201
|
+
* @throws {TypeError} When there is no field, a field's type has no fixed
|
|
202
|
+
* layout, a field is named like an array index or `~layout`, or a method is
|
|
203
|
+
* not a function or is named like a field, an array index or `~layout`.
|
|
204
|
+
*/
|
|
205
|
+
export declare const struct: <TFields extends StructFields, TMethods extends StructMethods = NoMethods>(name: string, fields: TFields, methods?: TMethods & ThisType<StructValue<TFields, TMethods>>) => StructType<TFields, TMethods>;
|
|
206
|
+
export {};
|