koffi 3.0.0 → 3.0.2
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 +20 -1
- package/README.md +5 -4
- package/cnoke.cjs +10 -10
- package/doc/benchmarks.md +24 -47
- package/doc/callbacks.md +4 -13
- package/doc/contribute.md +4 -4
- package/doc/index.md +3 -2
- package/doc/migration.md +100 -0
- package/doc/start.md +7 -5
- package/index.d.ts +53 -5
- package/index.js +2 -1
- package/indirect.js +2 -1
- package/package.json +20 -18
- package/src/koffi/CMakeLists.txt +20 -12
- package/src/koffi/index.cjs +92 -129
- package/src/koffi/index.js +128 -113
- package/src/koffi/indirect.cjs +91 -128
- package/src/koffi/indirect.js +129 -40
- package/src/koffi/src/abi/arm64.cc +30 -29
- package/src/koffi/src/abi/riscv64.cc +30 -29
- package/src/koffi/src/abi/x64sysv.cc +26 -25
- package/src/koffi/src/abi/x64win.cc +64 -63
- package/src/koffi/src/abi/x86.cc +67 -65
- package/src/koffi/src/call.cc +210 -99
- package/src/koffi/src/call.hh +2 -1
- package/src/koffi/src/ffi.cc +403 -237
- package/src/koffi/src/ffi.hh +46 -7
- package/src/koffi/src/parser.cc +3 -1
- package/src/koffi/src/primitives.inc +1 -4
- package/src/koffi/src/static.cjs +122 -0
- package/src/koffi/src/static.js +122 -0
- package/src/koffi/src/type.cc +715 -0
- package/src/koffi/src/type.hh +71 -0
- package/src/koffi/src/util.cc +189 -1120
- package/src/koffi/src/util.hh +85 -125
- package/src/koffi/src/uv.cc +16 -10
- package/src/koffi/src/uv.hh +2 -1
package/src/koffi/CMakeLists.txt
CHANGED
|
@@ -67,6 +67,7 @@ set(KOFFI_SRC
|
|
|
67
67
|
src/call.cc
|
|
68
68
|
src/ffi.cc
|
|
69
69
|
src/parser.cc
|
|
70
|
+
src/type.cc
|
|
70
71
|
src/util.cc
|
|
71
72
|
src/uv.cc
|
|
72
73
|
src/win32.cc
|
|
@@ -137,6 +138,7 @@ if(WIN32)
|
|
|
137
138
|
endif()
|
|
138
139
|
|
|
139
140
|
target_compile_definitions(koffi PRIVATE FELIX_TARGET=koffi NAPI_DISABLE_CPP_EXCEPTIONS NAPI_VERSION=NAPI_VERSION_EXPERIMENTAL)
|
|
141
|
+
|
|
140
142
|
if(WIN32)
|
|
141
143
|
target_compile_definitions(koffi PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
|
|
142
144
|
target_link_libraries(koffi PRIVATE ws2_32)
|
|
@@ -152,27 +154,33 @@ endif()
|
|
|
152
154
|
option(PGO_GENERATE "Build with PGO profile generation" "")
|
|
153
155
|
option(PGO_USE "Optimize with PGO profile" "")
|
|
154
156
|
|
|
155
|
-
if(MSVC
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if(CMAKE_C_COMPILER_ID MATCHES "[Cc]lang")
|
|
159
|
-
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /clang:-O3")
|
|
157
|
+
if(MSVC)
|
|
158
|
+
if (NOT CMAKE_C_COMPILER_ID MATCHES "[Cc]lang")
|
|
159
|
+
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/EHsc>)
|
|
160
160
|
endif()
|
|
161
161
|
|
|
162
|
-
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
163
|
-
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/
|
|
162
|
+
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
|
163
|
+
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/GS- /Ob2>)
|
|
164
|
+
|
|
165
|
+
if(CMAKE_C_COMPILER_ID MATCHES "[Cc]lang")
|
|
166
|
+
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /clang:-O3")
|
|
167
|
+
endif()
|
|
168
|
+
|
|
169
|
+
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
170
|
+
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/Oy->)
|
|
171
|
+
endif()
|
|
164
172
|
endif()
|
|
165
173
|
endif()
|
|
166
174
|
|
|
167
175
|
if(NOT MSVC OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang")
|
|
168
176
|
# Restore C/C++ compiler sanity
|
|
169
177
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions -fno-rtti -fno-strict-aliasing
|
|
179
|
+
-fno-delete-null-pointer-checks>)
|
|
180
|
+
if(MSVC)
|
|
181
|
+
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/clang:-fwrapv>)
|
|
173
182
|
else()
|
|
174
|
-
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-
|
|
175
|
-
-fno-delete-null-pointer-checks>)
|
|
183
|
+
target_compile_options(koffi PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fwrapv>)
|
|
176
184
|
endif()
|
|
177
185
|
|
|
178
186
|
check_cxx_compiler_flag(-fno-finite-loops use_no_finite_loops)
|
package/src/koffi/index.cjs
CHANGED
|
@@ -7,9 +7,6 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
7
7
|
var __esm = (fn, res) => function __init() {
|
|
8
8
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
9
|
};
|
|
10
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
11
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
|
-
};
|
|
13
10
|
var __export = (target, all) => {
|
|
14
11
|
for (var name in all)
|
|
15
12
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -32,9 +29,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
29
|
));
|
|
33
30
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
34
31
|
|
|
35
|
-
//
|
|
32
|
+
// src/cnoke/src/abi.js
|
|
36
33
|
function determineAbi() {
|
|
37
|
-
let abi = process.arch;
|
|
34
|
+
let abi = process.arch.toString();
|
|
38
35
|
if (abi == "riscv32" || abi == "riscv64") {
|
|
39
36
|
let buf = readFileHeader(process.execPath, 512);
|
|
40
37
|
let header = decodeElfHeader(buf);
|
|
@@ -122,20 +119,20 @@ function decodeElfHeader(buf) {
|
|
|
122
119
|
}
|
|
123
120
|
var import_node_fs;
|
|
124
121
|
var init_abi = __esm({
|
|
125
|
-
"
|
|
122
|
+
"src/cnoke/src/abi.js"() {
|
|
126
123
|
import_node_fs = __toESM(require("node:fs"), 1);
|
|
127
124
|
}
|
|
128
125
|
});
|
|
129
126
|
|
|
130
|
-
// package.json
|
|
127
|
+
// src/koffi/package.json
|
|
131
128
|
var package_default;
|
|
132
129
|
var init_package = __esm({
|
|
133
|
-
"package.json"() {
|
|
134
|
-
package_default = { name: "koffi", version: "3.0.
|
|
130
|
+
"src/koffi/package.json"() {
|
|
131
|
+
package_default = { name: "koffi", version: "3.0.2", cnoke: { api: "../../vendor/node-api-headers", output: "../../bin/Koffi/{{ toolchain }}", node: 16, napi: 8 } };
|
|
135
132
|
}
|
|
136
133
|
});
|
|
137
134
|
|
|
138
|
-
// src/init.js
|
|
135
|
+
// src/koffi/src/init.js
|
|
139
136
|
var init_exports = {};
|
|
140
137
|
__export(init_exports, {
|
|
141
138
|
detectPlatform: () => detectPlatform,
|
|
@@ -145,7 +142,7 @@ __export(init_exports, {
|
|
|
145
142
|
function detectPlatform() {
|
|
146
143
|
if (process.versions.napi == null || process.versions.napi < package_default.cnoke.napi) {
|
|
147
144
|
let major = parseInt(process.versions.node, 10);
|
|
148
|
-
throw new Error(`This engine is based on Node ${process.versions.node}, but ${package_default.name} does not support the Node ${major}.x branch (
|
|
145
|
+
throw new Error(`This engine is based on Node ${process.versions.node}, but ${package_default.name} does not support the Node ${major}.x branch (Node-API < ${package_default.cnoke.napi})`);
|
|
149
146
|
}
|
|
150
147
|
let abi = determineAbi();
|
|
151
148
|
let pkg2 = `${process.platform}-${process.arch}`;
|
|
@@ -154,13 +151,20 @@ function detectPlatform() {
|
|
|
154
151
|
triplets2.push(`musl_${abi}`);
|
|
155
152
|
return [package_default.version, pkg2, triplets2];
|
|
156
153
|
}
|
|
157
|
-
function loadDynamic(
|
|
154
|
+
function loadDynamic(dirname, pkg2, triplets2) {
|
|
155
|
+
let suffix = "/../../build/koffi";
|
|
156
|
+
let root = dirname + suffix;
|
|
158
157
|
let roots = [root];
|
|
159
158
|
let native2 = null;
|
|
160
159
|
let err = null;
|
|
161
|
-
if (process
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
if (process["resourcesPath"] != null) {
|
|
161
|
+
let suffixes = [
|
|
162
|
+
"/koffi",
|
|
163
|
+
"/koffi/build",
|
|
164
|
+
"/node_modules/koffi/build"
|
|
165
|
+
];
|
|
166
|
+
for (let suffix2 of suffixes)
|
|
167
|
+
roots.push(process["resourcesPath"] + suffix2);
|
|
164
168
|
}
|
|
165
169
|
let names = [
|
|
166
170
|
`${__dirname}/../../../@koromix/koffi-${pkg2}`,
|
|
@@ -170,42 +174,43 @@ function loadDynamic(root, pkg2, triplets2) {
|
|
|
170
174
|
if (!import_node_fs2.default.existsSync(name))
|
|
171
175
|
continue;
|
|
172
176
|
try {
|
|
173
|
-
native2 =
|
|
177
|
+
native2 = require2(name);
|
|
174
178
|
break;
|
|
175
179
|
} catch (e) {
|
|
176
180
|
err ??= e;
|
|
177
181
|
}
|
|
178
182
|
}
|
|
179
|
-
if (native2 == null)
|
|
180
|
-
err ??= new Error("Cannot find the native Koffi module; did you bundle it correctly?");
|
|
183
|
+
if (native2 == null && err != null)
|
|
181
184
|
throw err;
|
|
182
|
-
}
|
|
183
185
|
return native2;
|
|
184
186
|
}
|
|
185
|
-
function wrapNative(native2) {
|
|
187
|
+
function wrapNative(native2, version2) {
|
|
188
|
+
if (native2 == null)
|
|
189
|
+
throw new Error("Cannot find the native Koffi module; did you bundle it correctly?");
|
|
190
|
+
if (native2.version != version2)
|
|
191
|
+
throw new Error("Mismatched native Koffi modules");
|
|
186
192
|
let load = native2.load;
|
|
187
193
|
let register = native2.register;
|
|
188
|
-
|
|
189
|
-
native2.
|
|
194
|
+
let introspect = native2.introspect ?? native2.type;
|
|
195
|
+
native2.sizeof = (spec) => introspect(spec).size;
|
|
196
|
+
native2.alignof = (spec) => introspect(spec).alignment;
|
|
190
197
|
native2.offsetof = (spec, name) => {
|
|
191
|
-
let
|
|
192
|
-
if (
|
|
198
|
+
let info = introspect(spec);
|
|
199
|
+
if (info.primitive != "Record")
|
|
193
200
|
throw new TypeError("The offsetof() function can only be used with record types");
|
|
194
|
-
let member =
|
|
201
|
+
let member = info.members[name];
|
|
195
202
|
if (member == null)
|
|
196
|
-
throw new Error(`Record type ${
|
|
203
|
+
throw new Error(`Record type ${info.name} does not have member '${name}'`);
|
|
197
204
|
return member.offset;
|
|
198
205
|
};
|
|
199
206
|
native2.register = (...args) => {
|
|
200
207
|
if (args.length >= 3 && typeof args[1] == "function") {
|
|
201
|
-
process.emitWarning("Using koffi.register() with a custom this value was deprecated in Koffi
|
|
208
|
+
process.emitWarning("Using koffi.register() with a custom this value was deprecated in Koffi 2.17, use function.bind() instead", "DeprecationWarning", "KOFFI009");
|
|
202
209
|
args[1] = args[1].bind(args[0]);
|
|
203
210
|
args = args.slice(1);
|
|
204
211
|
}
|
|
205
212
|
return register(...args);
|
|
206
213
|
};
|
|
207
|
-
native2.resolve = import_node_util.default.deprecate(native2.type, "The koffi.resolve() function was deprecated in Koffi 3.0, use koffi.type() instead", "KOFFI007");
|
|
208
|
-
native2.introspect = import_node_util.default.deprecate(native2.type, "The koffi.introspect() function was deprecated in Koffi 3.0, use koffi.type() instead", "KOFFI008");
|
|
209
214
|
native2.load = (...args) => {
|
|
210
215
|
let lib = load(...args);
|
|
211
216
|
lib.cdecl = import_node_util.default.deprecate((...args2) => lib.func("__cdecl", ...args2), "The koffi.cdecl() function was deprecated in Koffi 2.7, use koffi.func(...) instead", "KOFFI003");
|
|
@@ -214,115 +219,73 @@ function wrapNative(native2) {
|
|
|
214
219
|
lib.thiscall = import_node_util.default.deprecate((...args2) => lib.func("__thiscall", ...args2), 'The koffi.thiscall() function was deprecated in Koffi 2.7, use koffi.func("__thiscall", ...) instead', "KOFFI006");
|
|
215
220
|
return lib;
|
|
216
221
|
};
|
|
222
|
+
if (native2.introspect == null) {
|
|
223
|
+
native2.resolve = import_node_util.default.deprecate(native2.type, "The koffi.resolve() function was deprecated in Koffi 3.0, use koffi.type() instead", "KOFFI007");
|
|
224
|
+
native2.introspect = import_node_util.default.deprecate(native2.type, "The koffi.introspect() function was deprecated in Koffi 3.0, use koffi.type() instead", "KOFFI008");
|
|
225
|
+
} else {
|
|
226
|
+
native2.resolve = native2.type;
|
|
227
|
+
}
|
|
217
228
|
}
|
|
218
|
-
var import_node_util, import_node_fs2, import_node_module,
|
|
229
|
+
var import_node_util, import_node_fs2, import_node_module, require2;
|
|
219
230
|
var init_init = __esm({
|
|
220
|
-
"src/init.js"() {
|
|
231
|
+
"src/koffi/src/init.js"() {
|
|
221
232
|
import_node_util = __toESM(require("node:util"));
|
|
222
233
|
import_node_fs2 = __toESM(require("node:fs"));
|
|
223
234
|
import_node_module = require("node:module");
|
|
224
235
|
init_abi();
|
|
225
236
|
init_package();
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// src/static.js
|
|
231
|
-
var require_static = __commonJS({
|
|
232
|
-
"src/static.js"(exports2, module2) {
|
|
233
|
-
var { createRequire: createRequire2 } = require("node:module");
|
|
234
|
-
var requireNative2 = createRequire2(__filename);
|
|
235
|
-
var BINARY_ROOT2 = __dirname + "/../../build/koffi";
|
|
236
|
-
function loadStatic2(pkg2) {
|
|
237
|
-
let native2 = null;
|
|
238
|
-
try {
|
|
239
|
-
if (pkg2 == "linux-arm64")
|
|
240
|
-
native2 = requireNative2("../../../@koromix/koffi-linux-arm64");
|
|
241
|
-
} catch (err) {
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
244
|
-
if (pkg2 == "linux-ia32")
|
|
245
|
-
native2 = requireNative2("../../../@koromix/koffi-linux-ia32");
|
|
246
|
-
} catch (err) {
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
if (pkg2 == "linux-x64")
|
|
250
|
-
native2 = requireNative2("../../../@koromix/koffi-linux-x64");
|
|
251
|
-
} catch (err) {
|
|
252
|
-
}
|
|
253
|
-
try {
|
|
254
|
-
if (pkg2 == "linux-riscv64")
|
|
255
|
-
native2 = requireNative2("../../../@koromix/koffi-linux-riscv64");
|
|
256
|
-
} catch (err) {
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
if (pkg2 == "freebsd-ia32")
|
|
260
|
-
native2 = requireNative2("../../../@koromix/koffi-freebsd-ia32");
|
|
261
|
-
} catch (err) {
|
|
262
|
-
}
|
|
263
|
-
try {
|
|
264
|
-
if (pkg2 == "freebsd-x64")
|
|
265
|
-
native2 = requireNative2("../../../@koromix/koffi-freebsd-x64");
|
|
266
|
-
} catch (err) {
|
|
267
|
-
}
|
|
268
|
-
try {
|
|
269
|
-
if (pkg2 == "freebsd-arm64")
|
|
270
|
-
native2 = requireNative2("../../../@koromix/koffi-freebsd-arm64");
|
|
271
|
-
} catch (err) {
|
|
272
|
-
}
|
|
273
|
-
try {
|
|
274
|
-
if (pkg2 == "openbsd-ia32")
|
|
275
|
-
native2 = requireNative2("../../../@koromix/koffi-openbsd-ia32");
|
|
276
|
-
} catch (err) {
|
|
277
|
-
}
|
|
278
|
-
try {
|
|
279
|
-
if (pkg2 == "openbsd-x64")
|
|
280
|
-
native2 = requireNative2("../../../@koromix/koffi-openbsd-x64");
|
|
281
|
-
} catch (err) {
|
|
282
|
-
}
|
|
283
|
-
try {
|
|
284
|
-
if (pkg2 == "win32-ia32")
|
|
285
|
-
native2 = requireNative2("../../../@koromix/koffi-win32-ia32");
|
|
286
|
-
} catch (err) {
|
|
287
|
-
}
|
|
288
|
-
try {
|
|
289
|
-
if (pkg2 == "win32-x64")
|
|
290
|
-
native2 = requireNative2("../../../@koromix/koffi-win32-x64");
|
|
291
|
-
} catch (err) {
|
|
292
|
-
}
|
|
293
|
-
try {
|
|
294
|
-
if (pkg2 == "darwin-x64")
|
|
295
|
-
native2 = requireNative2("../../../@koromix/koffi-darwin-x64");
|
|
296
|
-
} catch (err) {
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
if (pkg2 == "darwin-arm64")
|
|
300
|
-
native2 = requireNative2("../../../@koromix/koffi-darwin-arm64");
|
|
301
|
-
} catch (err) {
|
|
302
|
-
}
|
|
303
|
-
try {
|
|
304
|
-
if (pkg2 == "linux-loong64")
|
|
305
|
-
native2 = requireNative2("../../../@koromix/koffi-linux-loong64");
|
|
306
|
-
} catch (err) {
|
|
307
|
-
}
|
|
308
|
-
return native2;
|
|
309
|
-
}
|
|
310
|
-
module2.exports = {
|
|
311
|
-
BINARY_ROOT: BINARY_ROOT2,
|
|
312
|
-
loadStatic: loadStatic2
|
|
313
|
-
};
|
|
237
|
+
require2 = (0, import_node_module.createRequire)(__filename);
|
|
314
238
|
}
|
|
315
239
|
});
|
|
316
240
|
|
|
317
|
-
// index.cjs
|
|
241
|
+
// src/koffi/index.cjs
|
|
318
242
|
var { detectPlatform: detectPlatform2, loadDynamic: loadDynamic2, wrapNative: wrapNative2 } = (init_init(), __toCommonJS(init_exports));
|
|
319
|
-
var {
|
|
243
|
+
var { loadStatic } = require("./src/static.cjs");
|
|
320
244
|
var [version, pkg, triplets] = detectPlatform2();
|
|
321
|
-
var native =
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
native
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
245
|
+
var native = loadStatic(pkg) ?? loadDynamic2(__dirname, pkg, triplets);
|
|
246
|
+
wrapNative2(native, version);
|
|
247
|
+
module.exports = {
|
|
248
|
+
default: native,
|
|
249
|
+
"LibraryHandle": native["LibraryHandle"],
|
|
250
|
+
"TypeObject": native["TypeObject"],
|
|
251
|
+
"Union": native["Union"],
|
|
252
|
+
"address": native["address"],
|
|
253
|
+
"alias": native["alias"],
|
|
254
|
+
"alignof": native["alignof"],
|
|
255
|
+
"alloc": native["alloc"],
|
|
256
|
+
"array": native["array"],
|
|
257
|
+
"as": native["as"],
|
|
258
|
+
"call": native["call"],
|
|
259
|
+
"config": native["config"],
|
|
260
|
+
"decode": native["decode"],
|
|
261
|
+
"disposable": native["disposable"],
|
|
262
|
+
"encode": native["encode"],
|
|
263
|
+
"enumeration": native["enumeration"],
|
|
264
|
+
"errno": native["errno"],
|
|
265
|
+
"extension": native["extension"],
|
|
266
|
+
"free": native["free"],
|
|
267
|
+
"in": native["in"],
|
|
268
|
+
"inout": native["inout"],
|
|
269
|
+
"introspect": native["introspect"],
|
|
270
|
+
"load": native["load"],
|
|
271
|
+
"node": native["node"],
|
|
272
|
+
"offsetof": native["offsetof"],
|
|
273
|
+
"opaque": native["opaque"],
|
|
274
|
+
"os": native["os"],
|
|
275
|
+
"out": native["out"],
|
|
276
|
+
"pack": native["pack"],
|
|
277
|
+
"pointer": native["pointer"],
|
|
278
|
+
"proto": native["proto"],
|
|
279
|
+
"register": native["register"],
|
|
280
|
+
"reset": native["reset"],
|
|
281
|
+
"resolve": native["resolve"],
|
|
282
|
+
"sizeof": native["sizeof"],
|
|
283
|
+
"stats": native["stats"],
|
|
284
|
+
"struct": native["struct"],
|
|
285
|
+
"type": native["type"],
|
|
286
|
+
"types": native["types"],
|
|
287
|
+
"union": native["union"],
|
|
288
|
+
"unregister": native["unregister"],
|
|
289
|
+
"version": native["version"],
|
|
290
|
+
"view": native["view"]
|
|
291
|
+
};
|