koffi 2.12.5-beta.2 → 2.14.0-beta.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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@
5
5
 
6
6
  ## Koffi 2
7
7
 
8
+ ### Koffi 2.13
9
+
10
+ #### Koffi 2.13.0
11
+
12
+ *Released on 2025-08-07*
13
+
14
+ - Support anonymous function prototype types (`koffi.proto()`)
15
+ - Accept null or undefined name to define anonymous composite or pointer types
16
+
8
17
  ### Koffi 2.12
9
18
 
10
19
  #### Koffi 2.12.4
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/index.d.ts CHANGED
@@ -19,7 +19,13 @@
19
19
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
20
  // OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- export function load(path: string | null): IKoffiLib;
22
+ type LoadOptions = {
23
+ lazy?: boolean,
24
+ global?: boolean,
25
+ deep?: boolean
26
+ };
27
+
28
+ export function load(path: string | null, options?: LoadOptions): IKoffiLib;
23
29
 
24
30
  interface IKoffiCType { __brand: 'IKoffiCType' }
25
31
  interface IKoffiPointerCast { __brand: 'IKoffiPointerCast' }
@@ -65,17 +71,17 @@ export type KoffiFunc<T extends (...args: any) => any> = T & {
65
71
 
66
72
  export interface IKoffiLib {
67
73
  func(definition: string): KoffiFunction;
68
- func(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
69
- func(convention: string, name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
74
+ func(name: string | number, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
75
+ func(convention: string, name: string | number, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
70
76
 
71
77
  /** @deprecated */ cdecl(definition: string): KoffiFunction;
72
- /** @deprecated */ cdecl(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
78
+ /** @deprecated */ cdecl(name: string | number, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
73
79
  /** @deprecated */ stdcall(definition: string): KoffiFunction;
74
- /** @deprecated */ stdcall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
80
+ /** @deprecated */ stdcall(name: string | number, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
75
81
  /** @deprecated */ fastcall(definition: string): KoffiFunction;
76
- /** @deprecated */ fastcall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
82
+ /** @deprecated */ fastcall(name: string | number, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
77
83
  /** @deprecated */ thiscall(definition: string): KoffiFunction;
78
- /** @deprecated */ thiscall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
84
+ /** @deprecated */ thiscall(name: string | number, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
79
85
 
80
86
  symbol(name: string, type: TypeSpec): any;
81
87
 
@@ -99,15 +105,16 @@ export class Union {
99
105
  }
100
106
 
101
107
  export function array(ref: TypeSpec, len: number, hint?: ArrayHint | null): IKoffiCType;
108
+ export function array(ref: TypeSpec, countedBy: string, hint?: ArrayHint | null, len?: number | null): IKoffiCType;
102
109
 
103
110
  export function opaque(name: string | null | undefined): IKoffiCType;
104
111
  export function opaque(): IKoffiCType;
105
112
  /** @deprecated */ export function handle(name: string | null | undefined): IKoffiCType;
106
113
  /** @deprecated */ export function handle(): IKoffiCType;
107
114
 
108
- export function pointer(ref: TypeSpec): IKoffiCType;
115
+ export function pointer(ref: TypeSpec, countedBy?: string | null): IKoffiCType;
109
116
  export function pointer(ref: TypeSpec, count: number): IKoffiCType;
110
- export function pointer(name: string | null | undefined, ref: TypeSpec): IKoffiCType;
117
+ export function pointer(name: string | null | undefined, ref: TypeSpec, countedBy?: string | null): IKoffiCType;
111
118
  export function pointer(name: string | null | undefined, ref: TypeSpec, count: number): IKoffiCType;
112
119
 
113
120
  export function out(type: TypeSpec): IKoffiCType;
@@ -155,9 +162,22 @@ export function introspect(type: TypeSpec): TypeInfo;
155
162
 
156
163
  export function alias(name: string, type: TypeSpec): IKoffiCType;
157
164
 
158
- export function config(): Record<string, unknown>;
159
- export function config(cfg: Record<string, unknown>): Record<string, unknown>;
160
- export function stats(): Record<string, unknown>;
165
+ type KoffiConfig = {
166
+ sync_stack_size: number
167
+ sync_heap_size: number
168
+ async_stack_size: number
169
+ async_heap_size: number
170
+ resident_async_pools: number
171
+ max_async_calls: number
172
+ max_type_size: number
173
+ };
174
+ type KoffiStats = {
175
+ disposed: number
176
+ };
177
+
178
+ export function config(): KoffiConfig;
179
+ export function config(cfg: KoffiConfig): KoffiConfig;
180
+ export function stats(): KoffiStats;
161
181
 
162
182
  export function alloc(type: TypeSpec, length: number): any;
163
183
  export function free(value: any): void;
@@ -254,5 +274,10 @@ type PrimitiveTypes =
254
274
  | 'ushort'
255
275
  | 'void'
256
276
  | 'wchar'
257
- | 'wchar_t'
258
- export const types: Record<PrimitiveTypes, IKoffiCType>
277
+ | 'wchar_t';
278
+ export const types: Record<PrimitiveTypes, IKoffiCType>;
279
+
280
+ // Internal stuff, don't use!
281
+ export const node: {
282
+ env: { __brand: 'IKoffiNodeEnv' }
283
+ };
package/index.js CHANGED
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
4
4
  return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
5
5
  };
6
6
 
7
- // bin/Koffi/package/src/cnoke/src/tools.js
7
+ // ../../bin/Koffi/package/src/cnoke/src/tools.js
8
8
  var require_tools = __commonJS({
9
- "bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
9
+ "../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
10
10
  "use strict";
11
11
  var crypto = require("crypto");
12
12
  var fs2 = require("fs");
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
397
397
  }
398
398
  });
399
399
 
400
- // bin/Koffi/package/src/koffi/package.json
400
+ // ../../bin/Koffi/package/src/koffi/package.json
401
401
  var require_package = __commonJS({
402
- "bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
402
+ "../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
403
403
  module2.exports = {
404
404
  name: "koffi",
405
- version: "2.12.5-beta.2",
405
+ version: "2.14.0-beta.1",
406
406
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
407
407
  keywords: [
408
408
  "foreign",
@@ -443,9 +443,9 @@ var require_package = __commonJS({
443
443
  }
444
444
  });
445
445
 
446
- // bin/Koffi/package/src/koffi/src/init.js
446
+ // ../../bin/Koffi/package/src/koffi/src/init.js
447
447
  var require_init = __commonJS({
448
- "bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
448
+ "../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
449
449
  var fs = require("fs");
450
450
  var path = require("path");
451
451
  var util = require("util");
@@ -528,7 +528,7 @@ var require_init = __commonJS({
528
528
  }
529
529
  });
530
530
 
531
- // bin/Koffi/package/src/koffi/index.js
531
+ // ../../bin/Koffi/package/src/koffi/index.js
532
532
  var { detect: detect2, init: init2 } = require_init();
533
533
  var triplet2 = detect2();
534
534
  var native2 = null;
package/indirect.js CHANGED
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
4
4
  return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
5
5
  };
6
6
 
7
- // bin/Koffi/package/src/cnoke/src/tools.js
7
+ // ../../bin/Koffi/package/src/cnoke/src/tools.js
8
8
  var require_tools = __commonJS({
9
- "bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
9
+ "../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
10
10
  "use strict";
11
11
  var crypto = require("crypto");
12
12
  var fs2 = require("fs");
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
397
397
  }
398
398
  });
399
399
 
400
- // bin/Koffi/package/src/koffi/package.json
400
+ // ../../bin/Koffi/package/src/koffi/package.json
401
401
  var require_package = __commonJS({
402
- "bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
402
+ "../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
403
403
  module2.exports = {
404
404
  name: "koffi",
405
- version: "2.12.5-beta.2",
405
+ version: "2.14.0-beta.1",
406
406
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
407
407
  keywords: [
408
408
  "foreign",
@@ -443,9 +443,9 @@ var require_package = __commonJS({
443
443
  }
444
444
  });
445
445
 
446
- // bin/Koffi/package/src/koffi/src/init.js
446
+ // ../../bin/Koffi/package/src/koffi/src/init.js
447
447
  var require_init = __commonJS({
448
- "bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
448
+ "../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
449
449
  var fs = require("fs");
450
450
  var path = require("path");
451
451
  var util = require("util");
@@ -528,7 +528,7 @@ var require_init = __commonJS({
528
528
  }
529
529
  });
530
530
 
531
- // bin/Koffi/package/src/koffi/indirect.js
531
+ // ../../bin/Koffi/package/src/koffi/indirect.js
532
532
  var { detect: detect2, init: init2 } = require_init();
533
533
  var triplet2 = detect2();
534
534
  var mod2 = init2(triplet2, null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.12.5-beta.2",
3
+ "version": "2.14.0-beta.1",
4
4
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -232,6 +232,33 @@ static bool MapType(Napi::Env env, InstanceData *instance, const TypeInfo *type,
232
232
  return true;
233
233
  }
234
234
 
235
+ static bool CheckDynamicMembers(Napi::Env env, TypeInfo *type)
236
+ {
237
+ for (RecordMember &member: type->members) {
238
+ const char *countedby = member.type->countedby;
239
+
240
+ if (countedby) {
241
+ const RecordMember *by = std::find_if(type->members.begin(), type->members.end(),
242
+ [&](const RecordMember &member) { return TestStr(member.name, countedby); });
243
+
244
+ if (by == member.type->members.end()) {
245
+ ThrowError<Napi::Error>(env, "Record type %1 does not have member '%2'", type->name, countedby);
246
+ return false;
247
+ }
248
+ if (!IsInteger(by->type)) {
249
+ ThrowError<Napi::Error>(env, "Dynamic length member %1 is not an integer", countedby);
250
+ return false;
251
+ }
252
+
253
+ member.countedby = by - type->members.ptr;
254
+ } else {
255
+ member.countedby = -1;
256
+ }
257
+ }
258
+
259
+ return true;
260
+ }
261
+
235
262
  static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
236
263
  {
237
264
  Napi::Env env = info.Env();
@@ -372,6 +399,9 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
372
399
  type->members.Append(member);
373
400
  }
374
401
 
402
+ if (!CheckDynamicMembers(env, type))
403
+ return env.Null();
404
+
375
405
  size = (int32_t)AlignLen(size, type->align);
376
406
  if (!size) {
377
407
  ThrowError<Napi::Error>(env, "Empty struct '%1' is not allowed in C", type->name);
@@ -531,6 +561,9 @@ static Napi::Value CreateUnionType(const Napi::CallbackInfo &info)
531
561
  type->members.Append(member);
532
562
  }
533
563
 
564
+ if (!CheckDynamicMembers(env, type))
565
+ return env.Null();
566
+
534
567
  size = (int32_t)AlignLen(size, type->align);
535
568
  if (!size) {
536
569
  ThrowError<Napi::Error>(env, "Empty union '%1' is not allowed in C", type->name);
@@ -633,39 +666,46 @@ static Napi::Value CreatePointerType(const Napi::CallbackInfo &info)
633
666
 
634
667
  std::string name = named ? info[0].As<Napi::String>() : std::string();
635
668
 
636
- const TypeInfo *type = ResolveType(info[skip]);
637
- if (!type)
669
+ const TypeInfo *ref = ResolveType(info[skip]);
670
+ if (!ref)
638
671
  return env.Null();
639
672
 
640
- int count = 0;
641
- if (info.Length() >= 2u + skip) {
642
- if (!info[1 + skip].IsNumber()) {
643
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for count, expected number", GetValueType(instance, info[1 + skip]));
644
- return env.Null();
645
- }
673
+ Napi::Value countedby;
674
+ int count = 1;
646
675
 
647
- count = info[1 + skip].As<Napi::Number>();
676
+ if (info.Length() >= 2u + skip) {
677
+ if (info[1 + skip].IsString()) {
678
+ countedby = info[1 + skip];
679
+ } else if (info[1 + skip].IsNumber()) {
680
+ count = info[1 + skip].As<Napi::Number>();
648
681
 
649
- if (count < 1 || count > 4) {
650
- ThrowError<Napi::TypeError>(env, "Value of count must be between 1 and 4");
682
+ if (count < 1 || count > 4) {
683
+ ThrowError<Napi::TypeError>(env, "Value of count must be between 1 and 4");
684
+ return env.Null();
685
+ }
686
+ } else {
687
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for count, expected number", GetValueType(instance, info[1 + skip]));
651
688
  return env.Null();
652
689
  }
653
- } else {
654
- count = 1;
655
690
  }
656
691
 
657
- type = MakePointerType(instance, type, count);
692
+ TypeInfo *type = MakePointerType(instance, ref, count);
658
693
  RG_ASSERT(type);
659
694
 
660
- if (named) {
695
+ if (named || !countedby.IsEmpty()) {
661
696
  TypeInfo *copy = instance->types.AppendDefault();
662
697
  RG_DEFER_N(err_guard) { instance->types.RemoveLast(1); };
663
698
 
664
699
  memcpy((void *)copy, type, RG_SIZE(*type));
665
- copy->name = DuplicateString(name.c_str(), &instance->str_alloc).ptr;
700
+ copy->name = named ? DuplicateString(name.c_str(), &instance->str_alloc).ptr : copy->name;
701
+
702
+ if (!countedby.IsEmpty()) {
703
+ Napi::String str = countedby.As<Napi::String>();
704
+ copy->countedby = DuplicateString(str.Utf8Value().c_str(), &instance->str_alloc).ptr;
705
+ }
666
706
 
667
707
  // If the insert succeeds, we cannot fail anymore
668
- if (!MapType(env, instance, copy, copy->name))
708
+ if (named && !MapType(env, instance, copy, copy->name))
669
709
  return env.Null();
670
710
  err_guard.Disable();
671
711
 
@@ -931,29 +971,42 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
931
971
  InstanceData *instance = env.GetInstanceData<InstanceData>();
932
972
 
933
973
  if (info.Length() < 2) {
934
- ThrowError<Napi::TypeError>(env, "Expected 2 arguments, got %1", info.Length());
935
- return env.Null();
936
- }
937
- if (!info[1].IsNumber()) {
938
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for length, expected integer", GetValueType(instance, info[1]));
974
+ ThrowError<Napi::TypeError>(env, "Expected 2 to 3 arguments, got %1", info.Length());
939
975
  return env.Null();
940
976
  }
941
977
 
942
978
  const TypeInfo *ref = ResolveType(info[0]);
943
- int64_t len = info[1].As<Napi::Number>().Int64Value();
944
-
945
979
  if (!ref)
946
980
  return env.Null();
947
- if (len <= 0) {
981
+
982
+ int64_t len = 0;
983
+ Napi::Value countedby;
984
+
985
+ if (info[1].IsNumber()) {
986
+ len = info[1].As<Napi::Number>().Int64Value();
987
+ } else if (info[1].IsString()) {
988
+ countedby = info[1];
989
+
990
+ if (info.Length() >= 4 && !IsNullOrUndefined(info[3])) {
991
+ len = info[3].As<Napi::Number>().Int64Value();
992
+ }
993
+ } else {
994
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for length, expected integer or string", GetValueType(instance, info[1]));
995
+ }
996
+
997
+ if (countedby.IsEmpty() && len <= 0) {
948
998
  ThrowError<Napi::TypeError>(env, "Array length must be positive and non-zero");
949
999
  return env.Null();
1000
+ } else if (len < 0) {
1001
+ ThrowError<Napi::TypeError>(env, "Array length must be positive");
1002
+ return env.Null();
950
1003
  }
951
1004
  if (len > instance->config.max_type_size / ref->size) {
952
1005
  ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / ref->size);
953
1006
  return env.Null();
954
1007
  }
955
1008
 
956
- const TypeInfo *type = nullptr;
1009
+ TypeInfo *type = nullptr;
957
1010
 
958
1011
  if (info.Length() >= 3 && !IsNullOrUndefined(info[2])) {
959
1012
  if (!info[2].IsString()) {
@@ -990,6 +1043,11 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
990
1043
  type = MakeArrayType(instance, ref, len);
991
1044
  }
992
1045
 
1046
+ if (!countedby.IsEmpty()) {
1047
+ Napi::String str = countedby.As<Napi::String>();
1048
+ type->countedby = DuplicateString(str.Utf8Value().c_str(), &instance->str_alloc).ptr;
1049
+ }
1050
+
993
1051
  return WrapType(env, instance, type);
994
1052
  }
995
1053
 
@@ -1018,24 +1076,24 @@ static bool ParseClassicFunction(const Napi::CallbackInfo &info, bool concrete,
1018
1076
 
1019
1077
  bool named = parameters.IsArray();
1020
1078
 
1021
- if (!named) {
1022
- parameters = ret.As<Napi::Array>();
1023
- ret = name;
1024
- }
1025
-
1079
+ if (named) {
1026
1080
  #if defined(_WIN32)
1027
- if (name.IsNumber()) {
1028
- out_func->ordinal_name = name.As<Napi::Number>().Int32Value();
1029
- name = name.ToString();
1030
- }
1081
+ if (name.IsNumber()) {
1082
+ out_func->ordinal_name = name.As<Napi::Number>().Int32Value();
1083
+ name = name.ToString();
1084
+ }
1031
1085
  #endif
1032
- if (!name.IsString()) {
1033
- if (!concrete && IsNullOrUndefined(name)) {
1034
- named = false;
1035
- } else {
1036
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string or integer", GetValueType(instance, name));
1037
- return false;
1086
+ if (!name.IsString()) {
1087
+ if (!concrete && IsNullOrUndefined(name)) {
1088
+ named = false;
1089
+ } else {
1090
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string or integer", GetValueType(instance, name));
1091
+ return false;
1092
+ }
1038
1093
  }
1094
+ } else {
1095
+ parameters = ret.As<Napi::Array>();
1096
+ ret = name;
1039
1097
  }
1040
1098
 
1041
1099
  // Leave anonymous naming responsibility to caller
@@ -1334,6 +1392,9 @@ static Napi::Value GetTypeDefinition(const Napi::CallbackInfo &info)
1334
1392
  obj.Set("name", member.name);
1335
1393
  obj.Set("type", WrapType(env, instance, member.type));
1336
1394
  obj.Set("offset", member.offset);
1395
+ if (member.countedby >= 0) {
1396
+ obj.Set("countedBy", type->members[member.countedby].name);
1397
+ }
1337
1398
 
1338
1399
  members.Set(member.name, obj);
1339
1400
  }
@@ -147,6 +147,7 @@ struct TypeInfo {
147
147
  const FunctionInfo *proto; // Callback only
148
148
  } ref;
149
149
  ArrayHint hint; // Array only
150
+ const char *countedby; // Pointer or array
150
151
 
151
152
  mutable Napi::FunctionReference construct; // Union only
152
153
  mutable Napi::ObjectReference defn;
@@ -158,6 +159,7 @@ struct RecordMember {
158
159
  const char *name;
159
160
  const TypeInfo *type;
160
161
  int32_t offset;
162
+ Size countedby;
161
163
  };
162
164
 
163
165
  struct LibraryHolder {
@@ -164,9 +164,20 @@ const TypeInfo *PrototypeParser::ParseType(int *out_directions)
164
164
  }
165
165
  offset--;
166
166
 
167
+ if (out_directions && offset > start) {
168
+ int directions = ResolveDirections(tokens[start]);
169
+
170
+ if (directions) {
171
+ *out_directions = directions;
172
+ start++;
173
+ } else {
174
+ *out_directions = 1;
175
+ }
176
+ }
177
+
167
178
  while (offset >= start) {
168
179
  Span<const char> str = MakeSpan(tokens[start].ptr, tokens[offset].end() - tokens[start].ptr);
169
- const TypeInfo *type = ResolveType(env, str, out_directions);
180
+ const TypeInfo *type = ResolveType(env, str);
170
181
 
171
182
  if (type) {
172
183
  offset++;
@@ -110,6 +110,45 @@ void MagicUnion::Setter(const Napi::CallbackInfo &info, const Napi::Value &value
110
110
  raw.Clear();
111
111
  }
112
112
 
113
+ static inline bool IsIdentifierStart(char c)
114
+ {
115
+ return IsAsciiAlpha(c) || c == '_';
116
+ }
117
+
118
+ static inline bool IsIdentifierChar(char c)
119
+ {
120
+ return IsAsciiAlphaOrDigit(c) || c == '_';
121
+ }
122
+
123
+ static inline Span<const char> SplitIdentifier(Span<const char> str)
124
+ {
125
+ Size offset = 0;
126
+
127
+ if (str.len && IsIdentifierStart(str[0])) {
128
+ offset++;
129
+
130
+ while (offset < str.len && IsIdentifierChar(str[offset])) {
131
+ offset++;
132
+ }
133
+ }
134
+
135
+ Span<const char> token = str.Take(0, offset);
136
+ return token;
137
+ }
138
+
139
+ int ResolveDirections(Span<const char> str)
140
+ {
141
+ if (str == "_In_") {
142
+ return 1;
143
+ } else if (str == "_Out_") {
144
+ return 2;
145
+ } else if (str == "_Inout_") {
146
+ return 3;
147
+ } else {
148
+ return 0;
149
+ }
150
+ }
151
+
113
152
  const TypeInfo *ResolveType(Napi::Value value, int *out_directions)
114
153
  {
115
154
  Napi::Env env = value.Env();
@@ -117,12 +156,27 @@ const TypeInfo *ResolveType(Napi::Value value, int *out_directions)
117
156
 
118
157
  if (value.IsString()) {
119
158
  std::string str = value.As<Napi::String>();
159
+ Span<const char> remain = str.c_str();
120
160
 
121
161
  // Quick path for known types (int, float *, etc.)
122
- const TypeInfo *type = instance->types_map.FindValue(str.c_str(), nullptr);
162
+ const TypeInfo *type = instance->types_map.FindValue(remain.ptr, nullptr);
123
163
 
124
164
  if (!type || (type->flags & (int)TypeFlag::IsIncomplete)) {
125
- type = ResolveType(env, str.c_str(), out_directions);
165
+ if (out_directions) {
166
+ Span<const char> prefix = SplitIdentifier(remain);
167
+ int directions = ResolveDirections(prefix);
168
+
169
+ if (directions) {
170
+ remain = remain.Take(prefix.len, remain.len - prefix.len);
171
+ remain = TrimStrLeft(remain);
172
+
173
+ *out_directions = directions;
174
+ } else {
175
+ *out_directions = 1;
176
+ }
177
+ }
178
+
179
+ type = ResolveType(env, remain.ptr);
126
180
 
127
181
  if (!type) {
128
182
  if (!env.IsExceptionPending()) {
@@ -133,10 +187,10 @@ const TypeInfo *ResolveType(Napi::Value value, int *out_directions)
133
187
 
134
188
  // Cache for quick future access
135
189
  bool inserted;
136
- auto bucket = instance->types_map.TrySetDefault(str.c_str(), &inserted);
190
+ auto bucket = instance->types_map.TrySetDefault(remain.ptr, &inserted);
137
191
 
138
192
  if (inserted) {
139
- bucket->key = DuplicateString(str.c_str(), &instance->str_alloc).ptr;
193
+ bucket->key = DuplicateString(remain, &instance->str_alloc).ptr;
140
194
  bucket->value = type;
141
195
  }
142
196
  } else if (out_directions) {
@@ -155,6 +209,7 @@ const TypeInfo *ResolveType(Napi::Value value, int *out_directions)
155
209
  Size delta = (uint8_t *)raw - (uint8_t *)type;
156
210
  *out_directions = 1 + (int)delta;
157
211
  }
212
+
158
213
  return type;
159
214
  } else {
160
215
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value as type specifier, expected string or type", GetValueType(instance, value));
@@ -162,33 +217,7 @@ const TypeInfo *ResolveType(Napi::Value value, int *out_directions)
162
217
  }
163
218
  }
164
219
 
165
- static inline bool IsIdentifierStart(char c)
166
- {
167
- return IsAsciiAlpha(c) || c == '_';
168
- }
169
-
170
- static inline bool IsIdentifierChar(char c)
171
- {
172
- return IsAsciiAlphaOrDigit(c) || c == '_';
173
- }
174
-
175
- static inline Span<const char> SplitIdentifier(Span<const char> str)
176
- {
177
- Size offset = 0;
178
-
179
- if (str.len && IsIdentifierStart(str[0])) {
180
- offset++;
181
-
182
- while (offset < str.len && IsIdentifierChar(str[offset])) {
183
- offset++;
184
- }
185
- }
186
-
187
- Span<const char> token = str.Take(0, offset);
188
- return token;
189
- }
190
-
191
- const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_directions)
220
+ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str)
192
221
  {
193
222
  InstanceData *instance = env.GetInstanceData<InstanceData>();
194
223
 
@@ -196,28 +225,6 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
196
225
  LocalArray<Size, 8> arrays;
197
226
  uint8_t disposables = 0;
198
227
 
199
- // Consume parameter direction qualifier
200
- if (out_directions) {
201
- if (str.len && str[0] == '_') {
202
- Span<const char> qualifier = SplitIdentifier(str);
203
-
204
- if (qualifier == "_In_") {
205
- *out_directions = 1;
206
- str = str.Take(5, str.len - 5);
207
- } else if (qualifier == "_Out_") {
208
- *out_directions = 2;
209
- str = str.Take(6, str.len - 6);
210
- } else if (qualifier == "_Inout_") {
211
- *out_directions = 3;
212
- str = str.Take(8, str.len - 8);
213
- } else {
214
- *out_directions = 1;
215
- }
216
- } else {
217
- *out_directions = 1;
218
- }
219
- }
220
-
221
228
  Span<const char> name;
222
229
  Span<const char> after;
223
230
  {
@@ -383,7 +390,7 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
383
390
  return type;
384
391
  }
385
392
 
386
- const TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int count)
393
+ TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int count)
387
394
  {
388
395
  RG_ASSERT(count >= 1);
389
396
 
@@ -404,6 +411,7 @@ const TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int
404
411
  type->size = RG_SIZE(void *);
405
412
  type->align = RG_SIZE(void *);
406
413
  type->ref.type = ref;
414
+ type->hint = (ref->flags & (int)TypeFlag::HasTypedArray) ? ArrayHint::Typed : ArrayHint::Array;
407
415
  } else {
408
416
  type->primitive = PrimitiveKind::Callback;
409
417
  type->size = RG_SIZE(void *);
@@ -418,13 +426,12 @@ const TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int
418
426
  ref = bucket->value;
419
427
  }
420
428
 
421
- return ref;
429
+ return (TypeInfo *)ref;
422
430
  }
423
431
 
424
- static const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len,
425
- ArrayHint hint, bool insert)
432
+ static TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, ArrayHint hint, bool insert)
426
433
  {
427
- RG_ASSERT(len > 0);
434
+ RG_ASSERT(len >= 0);
428
435
  RG_ASSERT(len <= instance->config.max_type_size / ref->size);
429
436
 
430
437
  TypeInfo *type = instance->types.AppendDefault();
@@ -446,7 +453,7 @@ static const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref
446
453
  return type;
447
454
  }
448
455
 
449
- const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len)
456
+ TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len)
450
457
  {
451
458
  ArrayHint hint = {};
452
459
 
@@ -461,7 +468,7 @@ const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size
461
468
  return MakeArrayType(instance, ref, len, hint, true);
462
469
  }
463
470
 
464
- const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, ArrayHint hint)
471
+ TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, ArrayHint hint)
465
472
  {
466
473
  return MakeArrayType(instance, ref, len, hint, false);
467
474
  }
@@ -679,6 +686,86 @@ Napi::Object DecodeObject(Napi::Env env, const uint8_t *origin, const TypeInfo *
679
686
  return obj;
680
687
  }
681
688
 
689
+ static uint32_t DecodeDynamicLength(const uint8_t *origin, const RecordMember &by)
690
+ {
691
+ const uint8_t *src = origin + by.offset;
692
+
693
+ switch (by.type->primitive) {
694
+ case PrimitiveKind::Int8: {
695
+ int8_t i = *(int8_t *)src;
696
+ return (uint32_t)i;
697
+ } break;
698
+ case PrimitiveKind::UInt8: {
699
+ uint8_t u = *(uint8_t *)src;
700
+ return (uint32_t)u;
701
+ } break;
702
+ case PrimitiveKind::Int16: {
703
+ int16_t i = *(int16_t *)src;
704
+ return (uint32_t)i;
705
+ } break;
706
+ case PrimitiveKind::Int16S: {
707
+ int16_t i = ReverseBytes(*(int16_t *)src);
708
+ return (uint32_t)i;
709
+ } break;
710
+ case PrimitiveKind::UInt16: {
711
+ uint16_t u = *(uint16_t *)src;
712
+ return (uint32_t)u;
713
+ } break;
714
+ case PrimitiveKind::UInt16S: {
715
+ uint16_t u = ReverseBytes(*(uint16_t *)src);
716
+ return (uint32_t)u;
717
+ } break;
718
+ case PrimitiveKind::Int32: {
719
+ int32_t i = *(int32_t *)src;
720
+ return (uint32_t)i;
721
+ } break;
722
+ case PrimitiveKind::Int32S: {
723
+ int32_t i = ReverseBytes(*(int32_t *)src);
724
+ return (uint32_t)i;
725
+ } break;
726
+ case PrimitiveKind::UInt32: {
727
+ uint32_t u = *(uint32_t *)src;
728
+ return (uint32_t)u;
729
+ } break;
730
+ case PrimitiveKind::UInt32S: {
731
+ uint32_t u = ReverseBytes(*(uint32_t *)src);
732
+ return (uint32_t)u;
733
+ } break;
734
+ case PrimitiveKind::Int64: {
735
+ int64_t i = *(int64_t *)src;
736
+ return (uint32_t)i;
737
+ } break;
738
+ case PrimitiveKind::Int64S: {
739
+ int64_t i = ReverseBytes(*(int64_t *)src);
740
+ return (uint32_t)i;
741
+ } break;
742
+ case PrimitiveKind::UInt64: {
743
+ uint64_t u = *(uint64_t *)src;
744
+ return (uint32_t)u;
745
+ } break;
746
+ case PrimitiveKind::UInt64S: {
747
+ uint64_t u = ReverseBytes(*(uint64_t *)src);
748
+ return (uint32_t)u;
749
+ } break;
750
+
751
+ case PrimitiveKind::Void:
752
+ case PrimitiveKind::Bool:
753
+ case PrimitiveKind::String:
754
+ case PrimitiveKind::String16:
755
+ case PrimitiveKind::String32:
756
+ case PrimitiveKind::Pointer:
757
+ case PrimitiveKind::Callback:
758
+ case PrimitiveKind::Record:
759
+ case PrimitiveKind::Union:
760
+ case PrimitiveKind::Array:
761
+ case PrimitiveKind::Float32:
762
+ case PrimitiveKind::Float64:
763
+ case PrimitiveKind::Prototype: { RG_UNREACHABLE(); } break;
764
+ }
765
+
766
+ RG_UNREACHABLE();
767
+ }
768
+
682
769
  void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
683
770
  {
684
771
  Napi::Env env = obj.Env();
@@ -786,7 +873,13 @@ void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
786
873
  case PrimitiveKind::Callback: {
787
874
  void *ptr2 = *(void **)src;
788
875
 
789
- if (ptr2) {
876
+ if (member.countedby >= 0) {
877
+ const RecordMember &by = type->members[member.countedby];
878
+ uint32_t len = DecodeDynamicLength(origin, by);
879
+
880
+ Napi::Value value = DecodeArray(env, (const uint8_t *)ptr2, member.type, len);
881
+ obj.Set(member.name, value);
882
+ } else if (ptr2) {
790
883
  Napi::External<void> external = Napi::External<void>::New(env, ptr2);
791
884
  SetValueTag(instance, external, member.type->ref.marker);
792
885
 
@@ -805,8 +898,16 @@ void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
805
898
  obj.Set(member.name, obj2);
806
899
  } break;
807
900
  case PrimitiveKind::Array: {
808
- Napi::Value value = DecodeArray(env, src, member.type);
809
- obj.Set(member.name, value);
901
+ if (member.countedby >= 0) {
902
+ const RecordMember &by = type->members[member.countedby];
903
+ uint32_t len = DecodeDynamicLength(origin, by);
904
+
905
+ Napi::Value value = DecodeArray(env, src, member.type, len);
906
+ obj.Set(member.name, value);
907
+ } else {
908
+ Napi::Value value = DecodeArray(env, src, member.type);
909
+ obj.Set(member.name, value);
910
+ }
810
911
  } break;
811
912
  case PrimitiveKind::Float32: {
812
913
  float f = *(float *)src;
@@ -822,13 +923,9 @@ void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
822
923
  }
823
924
  }
824
925
 
825
- Napi::Value DecodeArray(Napi::Env env, const uint8_t *origin, const TypeInfo *type)
926
+ Napi::Value DecodeArray(Napi::Env env, const uint8_t *origin, const TypeInfo *type, uint32_t len)
826
927
  {
827
928
  InstanceData *instance = env.GetInstanceData<InstanceData>();
828
-
829
- RG_ASSERT(type->primitive == PrimitiveKind::Array);
830
-
831
- uint32_t len = type->size / type->ref.type->size;
832
929
  Size offset = 0;
833
930
 
834
931
  #define POP_ARRAY(SetCode) \
@@ -1013,6 +1110,14 @@ Napi::Value DecodeArray(Napi::Env env, const uint8_t *origin, const TypeInfo *ty
1013
1110
  RG_UNREACHABLE();
1014
1111
  }
1015
1112
 
1113
+ Napi::Value DecodeArray(Napi::Env env, const uint8_t *origin, const TypeInfo *type)
1114
+ {
1115
+ RG_ASSERT(type->primitive == PrimitiveKind::Array);
1116
+
1117
+ uint32_t len = type->size / type->ref.type->size;
1118
+ return DecodeArray(env, origin, type, len);
1119
+ }
1120
+
1016
1121
  void DecodeNormalArray(Napi::Array array, const uint8_t *origin, const TypeInfo *ref)
1017
1122
  {
1018
1123
  Napi::Env env = array.Env();
@@ -86,12 +86,13 @@ static inline bool IsRegularSize(Size size, Size max)
86
86
  return regular;
87
87
  }
88
88
 
89
+ int ResolveDirections(Span<const char> str);
89
90
  const TypeInfo *ResolveType(Napi::Value value, int *out_directions = nullptr);
90
- const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_directions = nullptr);
91
+ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str);
91
92
 
92
- const TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int count = 1);
93
- const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len);
94
- const TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, ArrayHint hint);
93
+ TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int count = 1);
94
+ TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len);
95
+ TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, ArrayHint hint);
95
96
 
96
97
  Napi::External<TypeInfo> WrapType(Napi::Env env, InstanceData *instance, const TypeInfo *type);
97
98
 
@@ -197,6 +198,7 @@ static inline Napi::String MakeStringFromUTF32(Napi::Env env, const char32_t *pt
197
198
 
198
199
  Napi::Object DecodeObject(Napi::Env env, const uint8_t *origin, const TypeInfo *type);
199
200
  void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type);
201
+ Napi::Value DecodeArray(Napi::Env env, const uint8_t *origin, const TypeInfo *type, uint32_t len);
200
202
  Napi::Value DecodeArray(Napi::Env env, const uint8_t *origin, const TypeInfo *type);
201
203
  void DecodeNormalArray(Napi::Array array, const uint8_t *origin, const TypeInfo *ref);
202
204
  void DecodeBuffer(Span<uint8_t> buffer, const uint8_t *origin, const TypeInfo *ref);