utilium 1.10.0 → 2.0.0-pre.1
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/buffer.d.ts +340 -1
- package/dist/buffer.js +58 -0
- package/dist/internal/primitives.d.ts +91 -18
- package/dist/internal/primitives.js +90 -25
- package/dist/internal/struct.d.ts +22 -48
- package/dist/internal/struct.js +7 -24
- package/dist/objects.d.ts +6 -0
- package/dist/string.js +7 -1
- package/dist/struct.d.ts +17 -18
- package/dist/struct.js +122 -217
- package/package.json +1 -1
- package/src/buffer.ts +363 -1
- package/src/internal/primitives.ts +120 -46
- package/src/internal/struct.ts +39 -72
- package/src/objects.ts +7 -0
- package/src/string.ts +7 -1
- package/src/struct.ts +152 -267
package/src/struct.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { BufferViewArray } from './buffer.js';
|
2
2
|
import { _debugLog } from './debugging.js';
|
3
3
|
import * as primitive from './internal/primitives.js';
|
4
4
|
import type {
|
@@ -6,27 +6,15 @@ import type {
|
|
6
6
|
Instance,
|
7
7
|
InstanceLike,
|
8
8
|
Member,
|
9
|
-
MemberContext,
|
10
|
-
MemberInit,
|
11
9
|
Metadata,
|
12
10
|
Options,
|
13
11
|
Size,
|
14
12
|
StaticLike,
|
15
13
|
TypeLike,
|
16
14
|
} from './internal/struct.js';
|
17
|
-
import {
|
18
|
-
_polyfill_metadata,
|
19
|
-
checkInstance,
|
20
|
-
checkStruct,
|
21
|
-
initMetadata,
|
22
|
-
isCustom,
|
23
|
-
isInstance,
|
24
|
-
isStatic,
|
25
|
-
isStruct,
|
26
|
-
} from './internal/struct.js';
|
15
|
+
import { checkStruct, initMetadata, isInstance, isStatic, isStruct } from './internal/struct.js';
|
27
16
|
import { _throw } from './misc.js';
|
28
|
-
import {
|
29
|
-
import type { ClassLike } from './types.js';
|
17
|
+
import { getAllPrototypes } from './objects.js';
|
30
18
|
export * as Struct from './internal/struct.js';
|
31
19
|
|
32
20
|
/**
|
@@ -49,43 +37,33 @@ export function sizeof<T extends TypeLike>(type: T | T[]): Size<T> {
|
|
49
37
|
if (typeof type == 'string') {
|
50
38
|
primitive.checkValid(type);
|
51
39
|
|
52
|
-
return
|
40
|
+
return primitive.types[primitive.normalize(type)].size as Size<T>;
|
53
41
|
}
|
54
42
|
|
55
|
-
if (
|
43
|
+
if (primitive.isType(type)) return type.size as Size<T>;
|
56
44
|
|
57
45
|
checkStruct(type);
|
58
46
|
|
59
47
|
const constructor = isStatic(type) ? type : type.constructor;
|
60
|
-
|
61
|
-
const { struct } = constructor[Symbol.metadata];
|
62
|
-
|
63
|
-
let size = struct.staticSize;
|
64
|
-
|
65
|
-
if (isStatic(type)) return size as Size<T>;
|
48
|
+
const { struct, structInit } = constructor[Symbol.metadata];
|
66
49
|
|
67
|
-
|
68
|
-
const value = (type as any)[member.name];
|
69
|
-
|
70
|
-
if (isInstance(value) && value.constructor[Symbol.metadata].struct.isDynamic) {
|
71
|
-
if (struct.isUnion) size = Math.max(size, sizeof(value));
|
72
|
-
else size += sizeof(value);
|
73
|
-
continue;
|
74
|
-
}
|
50
|
+
if (isStatic(type) || !struct.isDynamic) return struct.staticSize as Size<T>;
|
75
51
|
|
76
|
-
|
52
|
+
const last = structInit.members.at(-1)!;
|
53
|
+
const length = (type as any)[last.length as keyof typeof type];
|
54
|
+
let dynamicSize = 0;
|
77
55
|
|
78
|
-
|
56
|
+
if (primitive.isType(last.type)) {
|
57
|
+
dynamicSize = last.type.size * length;
|
58
|
+
} else {
|
59
|
+
const value = type[last.name];
|
79
60
|
|
80
|
-
for (let i = 0; i <
|
81
|
-
|
61
|
+
for (let i = 0; i < length; i++) {
|
62
|
+
dynamicSize += sizeof(isStruct(value[i]) ? value[i] : last.type);
|
82
63
|
}
|
83
|
-
|
84
|
-
if (struct.isUnion) size = Math.max(size, subSize);
|
85
|
-
else size += subSize;
|
86
64
|
}
|
87
65
|
|
88
|
-
return
|
66
|
+
return (struct.isUnion ? Math.max(struct.staticSize, dynamicSize) : struct.staticSize + dynamicSize) as Size<T>;
|
89
67
|
}
|
90
68
|
|
91
69
|
/**
|
@@ -96,31 +74,14 @@ export function offsetof(type: StaticLike | InstanceLike, memberName: string): n
|
|
96
74
|
|
97
75
|
const constructor = isStatic(type) ? type : type.constructor;
|
98
76
|
|
99
|
-
|
100
|
-
const { struct } = constructor[Symbol.metadata];
|
101
|
-
|
102
|
-
if (isStatic(type) || !struct.isDynamic) {
|
103
|
-
return (
|
104
|
-
struct.members.get(memberName)?.staticOffset
|
105
|
-
?? _throw(new Error('Struct does not have member: ' + memberName))
|
106
|
-
);
|
107
|
-
}
|
108
|
-
|
109
|
-
let offset = 0;
|
110
|
-
|
111
|
-
for (const member of struct.members.values()) {
|
112
|
-
if (member.name == memberName) return offset;
|
77
|
+
const member = constructor[Symbol.metadata].struct.members.get(memberName);
|
113
78
|
|
114
|
-
|
115
|
-
offset += sizeof(isStruct(value) ? value : member.type);
|
116
|
-
}
|
79
|
+
if (!member) throw new Error('Struct does not have member: ' + memberName);
|
117
80
|
|
118
|
-
|
81
|
+
return member.offset;
|
119
82
|
}
|
120
83
|
|
121
|
-
/**
|
122
|
-
* Aligns a number
|
123
|
-
*/
|
84
|
+
/** Aligns a number */
|
124
85
|
export function align(value: number, alignment: number): number {
|
125
86
|
return Math.ceil(value / alignment) * alignment;
|
126
87
|
}
|
@@ -129,68 +90,50 @@ export function align(value: number, alignment: number): number {
|
|
129
90
|
* Decorates a class as a struct
|
130
91
|
*/
|
131
92
|
export function struct(options: Partial<Options> = {}) {
|
132
|
-
return function _decorateStruct<
|
93
|
+
return function _decorateStruct<T extends StaticLike>(
|
133
94
|
target: T,
|
134
|
-
context: ClassDecoratorContext & DecoratorContext
|
135
|
-
):
|
136
|
-
const
|
137
|
-
|
138
|
-
let staticSize = 0,
|
139
|
-
isDynamic = false;
|
140
|
-
|
141
|
-
for (const { name, type, length } of initMetadata(context)) {
|
142
|
-
if (!primitive.isValid(type) && !isStatic(type)) throw new TypeError('Not a valid type: ' + type);
|
143
|
-
|
144
|
-
if (typeof length == 'string') {
|
145
|
-
const countedBy = members.get(length);
|
95
|
+
context: ClassDecoratorContext<T> & DecoratorContext
|
96
|
+
): void {
|
97
|
+
const init = initMetadata(context);
|
146
98
|
|
147
|
-
|
148
|
-
|
149
|
-
if (!primitive.isType(countedBy.type))
|
150
|
-
throw new Error(`"${length}" is not a number and cannot be used to count "${name}"`);
|
151
|
-
}
|
152
|
-
|
153
|
-
let decl = `${typeof type == 'string' ? type : type.name} ${name}`;
|
154
|
-
|
155
|
-
if (length !== undefined) decl += `[${length}]`;
|
156
|
-
|
157
|
-
members.set(name, {
|
158
|
-
name,
|
159
|
-
staticOffset: options.isUnion ? 0 : staticSize,
|
160
|
-
type: primitive.isValid(type) ? primitive.normalize(type) : type,
|
161
|
-
length,
|
162
|
-
decl,
|
163
|
-
});
|
99
|
+
const members = new Map<string, Member>();
|
164
100
|
|
165
|
-
|
166
|
-
|
167
|
-
? 0
|
168
|
-
: sizeof(type) * (length || 1);
|
101
|
+
for (const member of init.members) {
|
102
|
+
if (options.isUnion) member.offset = 0;
|
169
103
|
|
170
|
-
|
171
|
-
staticSize = options.isUnion ? Math.max(staticSize, memberSize) : staticSize + memberSize;
|
172
|
-
staticSize = align(staticSize, options.align || 1);
|
104
|
+
_debugLog('define', target.name + '.' + member.name);
|
173
105
|
|
174
|
-
|
106
|
+
members.set(member.name, member);
|
175
107
|
}
|
176
108
|
|
177
109
|
context.metadata.struct = {
|
178
110
|
options,
|
179
111
|
members,
|
180
|
-
staticSize,
|
181
|
-
isDynamic,
|
112
|
+
staticSize: init.size,
|
113
|
+
isDynamic: init.isDynamic,
|
182
114
|
isUnion: options.isUnion ?? false,
|
183
115
|
} satisfies Metadata;
|
184
|
-
|
185
|
-
return target;
|
186
116
|
};
|
187
117
|
}
|
188
118
|
|
119
|
+
export interface MemberOptions {
|
120
|
+
bigEndian?: boolean;
|
121
|
+
length?: number | string;
|
122
|
+
align?: number;
|
123
|
+
typeName?: string;
|
124
|
+
}
|
125
|
+
|
189
126
|
/**
|
190
127
|
* Decorates a class member to be serialized
|
191
128
|
*/
|
192
|
-
export function member(type: primitive.
|
193
|
-
return function
|
129
|
+
export function member<V>(type: primitive.Type | StaticLike, opt: MemberOptions = {}) {
|
130
|
+
return function __decorateMember(value: Target<V>, context: Context<V>): Result<V> {
|
131
|
+
if (context.kind != 'accessor') throw new Error('Member must be an accessor');
|
132
|
+
|
133
|
+
const init = initMetadata(context);
|
134
|
+
|
135
|
+
if (init.isDynamic) throw new Error('Dynamic members must be declared at the end of the struct');
|
136
|
+
|
194
137
|
let name = context.name;
|
195
138
|
if (typeof name == 'symbol') {
|
196
139
|
console.warn('Symbol used for struct member name will be coerced to string: ' + name.toString());
|
@@ -199,199 +142,141 @@ export function member(type: primitive.Valid | ClassLike, length?: number | stri
|
|
199
142
|
|
200
143
|
if (!name) throw new ReferenceError('Invalid name for struct member');
|
201
144
|
|
202
|
-
|
203
|
-
return value;
|
204
|
-
};
|
205
|
-
}
|
145
|
+
if (!primitive.isType(type) && !isStatic(type)) throw new TypeError('Not a valid type: ' + type.name);
|
206
146
|
|
207
|
-
|
208
|
-
|
209
|
-
if (member.length === undefined) return -1;
|
210
|
-
if (typeof member.length == 'string') return (instance as any)[member.length];
|
211
|
-
return Number.isSafeInteger(member.length) && member.length >= 0
|
212
|
-
? member.length
|
213
|
-
: _throw(new Error('Array lengths must be natural numbers'));
|
214
|
-
}
|
147
|
+
if (typeof opt.length == 'string') {
|
148
|
+
const countedBy = init.members.find(m => m.name == opt.length);
|
215
149
|
|
216
|
-
|
217
|
-
* Serializes a struct into a Uint8Array
|
218
|
-
*/
|
219
|
-
export function serialize(instance: unknown): Uint8Array {
|
220
|
-
if (isCustom(instance) && typeof instance[Symbol.serialize] == 'function') return instance[Symbol.serialize]!();
|
150
|
+
if (!countedBy) throw new Error(`"${opt.length}" is not declared and cannot be used to count "${name}"`);
|
221
151
|
|
222
|
-
|
223
|
-
|
224
|
-
const { options, members } = instance.constructor[Symbol.metadata].struct;
|
152
|
+
if (!primitive.isType(countedBy.type))
|
153
|
+
throw new Error(`"${opt.length}" is not a number and cannot be used to count "${name}"`);
|
225
154
|
|
226
|
-
|
227
|
-
|
228
|
-
const view = new DataView(buffer.buffer);
|
155
|
+
init.isDynamic = true;
|
156
|
+
}
|
229
157
|
|
230
|
-
|
158
|
+
const size = align(
|
159
|
+
sizeof(type) * (typeof opt.length == 'string' ? 0 : (opt.length ?? 1)),
|
160
|
+
opt.align ?? sizeof(type)
|
161
|
+
);
|
231
162
|
|
232
|
-
|
233
|
-
|
163
|
+
const member = {
|
164
|
+
name,
|
165
|
+
offset: init.size,
|
166
|
+
type,
|
167
|
+
length: opt.length,
|
168
|
+
size,
|
169
|
+
decl: `${opt.typeName ?? type.name} ${name}${opt.length !== undefined ? `[${JSON.stringify(opt.length)}]` : ''}`,
|
170
|
+
littleEndian: !opt.bigEndian,
|
171
|
+
} satisfies Member;
|
172
|
+
|
173
|
+
init.members.push(member);
|
174
|
+
|
175
|
+
// Apply after setting `offset`
|
176
|
+
init.size += size;
|
177
|
+
|
178
|
+
return {
|
179
|
+
get() {
|
180
|
+
return _get(this, member);
|
181
|
+
},
|
182
|
+
set(value) {
|
183
|
+
_set(this, member, value);
|
184
|
+
},
|
185
|
+
};
|
186
|
+
};
|
187
|
+
}
|
234
188
|
|
235
|
-
|
236
|
-
|
237
|
-
|
189
|
+
/** Gets the length of a member */
|
190
|
+
function _memberLength<T extends Metadata>(instance: Instance<T>, length?: number | string): number {
|
191
|
+
if (length === undefined) return -1;
|
192
|
+
if (typeof length == 'string') return instance[length];
|
193
|
+
return Number.isSafeInteger(length) && length >= 0
|
194
|
+
? length
|
195
|
+
: _throw(new Error('Array lengths must be natural numbers'));
|
196
|
+
}
|
238
197
|
|
239
|
-
|
198
|
+
function _set(instance: Instance, member: Member, value: any, index?: number) {
|
199
|
+
const { name, type, length: rawLength } = member;
|
200
|
+
const length = _memberLength(instance, rawLength);
|
240
201
|
|
241
|
-
|
242
|
-
|
243
|
-
if (typeof value == 'string') {
|
244
|
-
value = value.charCodeAt(0);
|
245
|
-
}
|
202
|
+
if (!primitive.isType(type)) {
|
203
|
+
if (!isInstance(value)) return _debugLog(`Tried to set "${name}" to a non-instance value`);
|
246
204
|
|
247
|
-
|
248
|
-
|
205
|
+
if (length > 0 && typeof index != 'number') {
|
206
|
+
for (let i = 0; i < length; i++) _set(instance, member, value[i], i);
|
207
|
+
return;
|
208
|
+
}
|
249
209
|
|
250
|
-
|
251
|
-
|
252
|
-
continue;
|
253
|
-
}
|
210
|
+
if (!Array.from(getAllPrototypes(value.constructor)).some(c => c === type))
|
211
|
+
throw new Error(`${value.constructor.name} is not a subtype of ${type.name}`);
|
254
212
|
|
255
|
-
|
213
|
+
const offset = instance.byteOffset + member.offset + (index ?? 0) * sizeof(type);
|
256
214
|
|
257
|
-
|
258
|
-
|
259
|
-
continue;
|
260
|
-
}
|
215
|
+
// It's already the same value
|
216
|
+
if (value.buffer === instance.buffer && value.byteOffset === offset) return;
|
261
217
|
|
262
|
-
|
263
|
-
view.setBigUint64(offset, BigInt(value), !options.bigEndian);
|
264
|
-
continue;
|
265
|
-
}
|
218
|
+
const current = new Uint8Array(instance.buffer, offset, sizeof(value));
|
266
219
|
|
267
|
-
|
268
|
-
view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
|
269
|
-
view.setBigInt64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
|
270
|
-
continue;
|
271
|
-
}
|
220
|
+
current.set(new Uint8Array(value.buffer, value.byteOffset, sizeof(value)));
|
272
221
|
|
273
|
-
|
274
|
-
|
275
|
-
view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
|
276
|
-
continue;
|
277
|
-
}
|
222
|
+
return;
|
223
|
+
}
|
278
224
|
|
279
|
-
|
280
|
-
view.setFloat64(offset + (!options.bigEndian ? 0 : 8), Number(value), !options.bigEndian);
|
281
|
-
view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), BigInt(0), !options.bigEndian);
|
282
|
-
continue;
|
283
|
-
}
|
225
|
+
const view = new DataView(instance.buffer, instance.byteOffset, instance.byteLength);
|
284
226
|
|
285
|
-
|
227
|
+
if (length > 0 && typeof index != 'number') {
|
228
|
+
for (let i = 0; i < length; i++) {
|
229
|
+
const offset = member.offset + i * type.size;
|
230
|
+
type.set(view, offset, member.littleEndian, value[i]);
|
286
231
|
}
|
232
|
+
return;
|
287
233
|
}
|
288
234
|
|
289
|
-
|
290
|
-
}
|
291
|
-
|
292
|
-
/**
|
293
|
-
* Deserializes a struct from a Uint8Array
|
294
|
-
*/
|
295
|
-
export function deserialize(instance: unknown, _buffer: ArrayBufferLike | ArrayBufferView) {
|
296
|
-
const buffer = toUint8Array(_buffer);
|
297
|
-
|
298
|
-
if (isCustom(instance) && typeof instance[Symbol.deserialize] == 'function')
|
299
|
-
return instance[Symbol.deserialize]!(buffer);
|
300
|
-
|
301
|
-
checkInstance(instance);
|
302
|
-
_polyfill_metadata(instance.constructor);
|
303
|
-
const { options, members } = instance.constructor[Symbol.metadata].struct;
|
304
|
-
|
305
|
-
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
306
|
-
|
307
|
-
_debugLog('deserialize', instance.constructor.name);
|
308
|
-
|
309
|
-
let offset = 0,
|
310
|
-
nextOffset = 0;
|
235
|
+
if (typeof value == 'string') value = value.charCodeAt(0);
|
311
236
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
_debugLog('\t', member.decl);
|
316
|
-
|
317
|
-
for (let i = 0; i < Math.abs(length); i++) {
|
318
|
-
let object = length != -1 ? instance[member.name] : instance;
|
319
|
-
const key = length != -1 ? i : member.name;
|
320
|
-
|
321
|
-
const isNullish = object[key] === null || object[key] === undefined;
|
322
|
-
|
323
|
-
const needsAllocation = isNullish && isStatic(member.type) && member.type[Symbol.metadata].struct.isDynamic;
|
324
|
-
|
325
|
-
offset = nextOffset;
|
326
|
-
if (!isInstance(object[key]) && !needsAllocation) nextOffset += sizeof(member.type);
|
327
|
-
|
328
|
-
if (typeof instance[member.name] == 'string') {
|
329
|
-
instance[member.name] =
|
330
|
-
instance[member.name].slice(0, i)
|
331
|
-
+ String.fromCharCode(view.getUint8(offset))
|
332
|
-
+ instance[member.name].slice(i + 1);
|
333
|
-
continue;
|
334
|
-
}
|
335
|
-
|
336
|
-
if (!primitive.isType(member.type)) {
|
337
|
-
if (needsAllocation && isStatic(member.type)) object[key] ??= new member.type();
|
338
|
-
else if (isNullish) continue;
|
339
|
-
|
340
|
-
deserialize(object[key], new Uint8Array(buffer.subarray(offset)));
|
341
|
-
nextOffset += sizeof(object[key]);
|
342
|
-
continue;
|
343
|
-
}
|
344
|
-
|
345
|
-
if (length && length != -1) object ||= [];
|
346
|
-
|
347
|
-
const fn = `get${capitalize(member.type)}` as const;
|
348
|
-
if (fn == 'getInt64') {
|
349
|
-
object[key] = view.getBigInt64(offset, !options.bigEndian);
|
350
|
-
continue;
|
351
|
-
}
|
237
|
+
type.set(view, member.offset + (index ?? 0) * type.size, member.littleEndian, value);
|
238
|
+
}
|
352
239
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
}
|
240
|
+
function _get(instance: Instance, member: Member, index?: number) {
|
241
|
+
const { type, length: rawLength } = member;
|
242
|
+
const length = _memberLength(instance, rawLength);
|
357
243
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
244
|
+
if (length > 0 && typeof index != 'number') {
|
245
|
+
return new (primitive.isType(type) ? type.array : BufferViewArray(type, sizeof(type)))(
|
246
|
+
instance.buffer,
|
247
|
+
instance.byteOffset + member.offset,
|
248
|
+
length * sizeof(type)
|
249
|
+
);
|
250
|
+
}
|
364
251
|
|
365
|
-
|
366
|
-
object[key] =
|
367
|
-
(view.getBigUint64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
|
368
|
-
| view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
|
369
|
-
continue;
|
370
|
-
}
|
252
|
+
const offset = member.offset + (index ?? 0) * sizeof(type);
|
371
253
|
|
372
|
-
|
373
|
-
object[key] = view.getFloat64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
|
374
|
-
continue;
|
375
|
-
}
|
254
|
+
if (isStatic(type)) return new type(instance.buffer, offset, sizeof(type));
|
376
255
|
|
377
|
-
|
378
|
-
|
379
|
-
}
|
256
|
+
const view = new DataView(instance.buffer, instance.byteOffset, instance.byteLength);
|
257
|
+
return type.get(view, offset, member.littleEndian);
|
380
258
|
}
|
381
259
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
260
|
+
// Decorator utility types
|
261
|
+
type Target<V> = ClassAccessorDecoratorTarget<any, V>;
|
262
|
+
type Result<V> = ClassAccessorDecoratorResult<any, V>;
|
263
|
+
type Context<V> = ClassAccessorDecoratorContext<any, V> & DecoratorContext;
|
264
|
+
type Decorator<V> = (value: Target<V>, context: Context<V>) => Result<V>;
|
265
|
+
|
266
|
+
function _member<T extends primitive.Valid>(typeName: T) {
|
267
|
+
const type = primitive.types[primitive.normalize(typeName)];
|
268
|
+
|
269
|
+
function _structMemberDecorator<V>(length: number | string): Decorator<V>;
|
270
|
+
function _structMemberDecorator<V>(value: Target<V>, context: Context<V>): Result<V>;
|
271
|
+
function _structMemberDecorator<V>(
|
272
|
+
valueOrLength: Target<V> | number | string,
|
273
|
+
context?: Context<V>
|
274
|
+
): Decorator<V> | Result<V> {
|
275
|
+
return typeof valueOrLength == 'number' || typeof valueOrLength == 'string'
|
276
|
+
? member<V>(type, { length: valueOrLength, typeName })
|
277
|
+
: member<V>(type, { typeName })(valueOrLength, context!);
|
394
278
|
}
|
279
|
+
|
395
280
|
return _structMemberDecorator;
|
396
281
|
}
|
397
282
|
|