quickwin 1.3.0 → 2.0.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/README.en.md +1 -2
- package/README.md +19 -23
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20054 -1423
- package/examples/exec_server.tsx +489 -0
- package/examples/exec_server_worker.js +383 -33
- package/examples/exec_server_worker.ts +408 -30
- package/examples/pdf_preview2.js +25 -82
- package/examples/pdf_preview2.ts +27 -84
- package/examples/pdf_viewer.js +1318 -1178
- package/examples/pdf_viewer.tsx +16 -67
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1313 -1074
- package/examples/test_react_lazy.js +1284 -1056
- package/examples/test_react_listbox.js +1283 -1055
- package/examples/test_root_window.js +1279 -1051
- package/examples/test_tab.js +1284 -1056
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +214 -0
- package/lib/certs.ts +224 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +37 -4
- package/lib/fetch.ts +35 -4
- package/lib/ffi/bind.js +352 -0
- package/lib/ffi/bind.ts +442 -0
- package/lib/ffi/ctype.js +168 -0
- package/lib/ffi/ctype.ts +331 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +412 -0
- package/lib/ffi/struct.ts +664 -0
- package/lib/http-common.js +43 -5
- package/lib/http-common.ts +48 -6
- package/lib/http-server.js +187 -29
- package/lib/http-server.ts +177 -26
- package/lib/react-qw/components/DateTimePicker.js +18 -37
- package/lib/react-qw/components/Input.js +3 -10
- package/lib/react-qw/components/Link.js +6 -19
- package/lib/react-qw/components/ListView.js +49 -154
- package/lib/react-qw/components/PathPicker.js +28 -88
- package/lib/react-qw/components/ScrollView.js +15 -8
- package/lib/react-qw/components/Slider.js +2 -1
- package/lib/react-qw/components/Tab.js +6 -22
- package/lib/react-qw/components/Tooltip.js +10 -21
- package/lib/react-qw/components/TreeView.js +8 -31
- package/lib/react-qw/index.js +4 -2
- package/lib/react-qw/index.ts +4 -2
- package/lib/react-qw/nmhdr.js +3 -24
- package/lib/react-qw/nmhdr.ts +5 -30
- package/lib/react-qw/props.js +7 -2
- package/lib/react-qw/props.ts +7 -3
- package/lib/react-qw/reconciler.js +23 -13
- package/lib/react-qw/reconciler.ts +22 -13
- package/lib/text-measure.js +18 -23
- package/lib/text-measure.ts +20 -25
- package/lib/websocket.js +12 -2
- package/lib/websocket.ts +15 -2
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +20 -0
- package/lib/windows/comdlg32.js +7 -0
- package/lib/windows/comdlg32.ts +9 -0
- package/lib/windows/gdi32.js +90 -0
- package/lib/windows/gdi32.ts +97 -0
- package/lib/windows/kernel32.js +66 -0
- package/lib/windows/kernel32.ts +76 -0
- package/lib/windows/ntdll.js +8 -0
- package/lib/windows/ntdll.ts +10 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/shell32.js +11 -0
- package/lib/windows/shell32.ts +14 -0
- package/lib/windows/structs.js +384 -0
- package/lib/windows/structs.ts +422 -0
- package/lib/windows/user32.js +469 -0
- package/lib/windows/user32.ts +502 -0
- package/package.json +1 -1
- package/quickwin.d.ts +22 -37
- package/quickwin_const.d.ts +1 -0
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/bench_ffi_callraw.js +236 -0
- package/test/bench_ffi_callraw.ts +236 -0
- package/test/bench_ffi_overhead.js +139 -0
- package/test/bench_ffi_overhead.ts +141 -0
- package/test/bench_sendmessage.js +104 -0
- package/test/bench_sendmessage.ts +100 -0
- package/test/bench_sendmessage_nonnull.js +187 -0
- package/test/bench_sendmessage_nonnull.ts +188 -0
- package/test/run.js +12 -4
- package/test/run.ts +13 -5
- package/test/test_basic_fetch.js +8 -0
- package/test/test_basic_fetch.ts +9 -0
- package/test/test_ffi.js +25 -43
- package/test/test_ffi.ts +33 -52
- package/test/test_ffi_abi.js +235 -0
- package/test/test_ffi_abi.ts +218 -0
- package/test/test_ffi_bind.js +538 -8
- package/test/test_ffi_bind.ts +559 -8
- package/test/test_ffi_struct.js +632 -88
- package/test/test_ffi_struct.ts +666 -91
- package/test/test_ffi_struct_layout.js +495 -0
- package/test/test_ffi_struct_layout.ts +543 -0
- package/test/test_ffi_user32.js +103 -0
- package/test/test_ffi_user32.ts +113 -0
- package/test/test_ffi_windows.js +101 -0
- package/test/test_ffi_windows.ts +113 -0
- package/test/test_helper.js +8 -1
- package/test/test_helper.ts +9 -1
- package/test/test_http_import.js +18 -0
- package/test/test_http_import.ts +19 -0
- package/test/test_http_server.js +73 -1
- package/test/test_http_server.ts +75 -1
- package/test/test_ipv6.js +2 -2
- package/test/test_ipv6.ts +2 -2
- package/test/test_net_fetch.js +70 -8
- package/test/test_net_fetch.ts +69 -7
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_react_complex.js +1294 -1066
- package/test/test_react_counter.js +1284 -1056
- package/test/test_react_flex.js +1304 -1069
- package/test/test_react_hidden.js +1287 -1059
- package/test/test_react_listview_link.js +1284 -1056
- package/test/test_react_notify.js +18534 -0
- package/test/test_react_render.js +1284 -1056
- package/test/test_wolfssl_read.js +3 -2
- package/test/test_wolfssl_read.ts +3 -1
- package/test/test_worker_net.js +35 -17
- package/test/test_worker_net.ts +27 -15
- package/tsconfig.json +1 -1
- package/examples/exec_server.ts +0 -80
- package/lib/ffi-bind.js +0 -85
- package/lib/ffi-bind.ts +0 -170
- package/lib/ffi-struct.js +0 -224
- package/lib/ffi-struct.ts +0 -239
- package/lib/win-common-structs.js +0 -39
- package/lib/win-common-structs.ts +0 -41
package/test/test_ffi_struct.js
CHANGED
|
@@ -1,50 +1,82 @@
|
|
|
1
1
|
import * as os from 'os';
|
|
2
|
-
import { struct } from '../lib/ffi
|
|
3
|
-
import { bind } from '../lib/ffi
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { struct, union } from '../lib/ffi/struct.js';
|
|
3
|
+
import { bind, } from '../lib/ffi/bind.js';
|
|
4
|
+
import { PtrArrayBuffer } from '../lib/ffi/ctype.js';
|
|
5
|
+
function expectType(_value) { }
|
|
6
|
+
// Ptr 定义:'' 判右侧(不作 naked 分布)——字面品牌照旧收窄、Ptr<''> 归一 number、
|
|
7
|
+
// never 不再塌缩成 never(旧定义下 Ptr<never> 即 never,decode(… | Ptr<N>) 会在 N=never 时堵死)
|
|
8
|
+
expectType(true);
|
|
9
|
+
expectType(true);
|
|
10
|
+
const RECT = /* @__PURE__ */ struct('RECT', {
|
|
11
|
+
left: 'i32',
|
|
12
|
+
top: 'i32',
|
|
13
|
+
right: 'i32',
|
|
14
|
+
bottom: 'i32',
|
|
15
|
+
});
|
|
16
|
+
const TVITEM = /* @__PURE__ */ struct('TVITEM', {
|
|
6
17
|
mask: 'u32',
|
|
7
|
-
hItem: 'ptr',
|
|
18
|
+
hItem: '<>ptr',
|
|
8
19
|
state: 'u32',
|
|
9
20
|
stateMask: 'u32',
|
|
10
|
-
pszText: 'ptr',
|
|
21
|
+
pszText: '<>ptr',
|
|
11
22
|
cchTextMax: 'i32',
|
|
12
23
|
iImage: 'i32',
|
|
13
24
|
iSelectedImage: 'i32',
|
|
14
25
|
cChildren: 'i32',
|
|
15
|
-
lParam: 'ptr',
|
|
26
|
+
lParam: '<>ptr',
|
|
27
|
+
});
|
|
28
|
+
const TVINSERTSTRUCT = /* @__PURE__ */ struct('TVINSERTSTRUCT', {
|
|
29
|
+
hParent: '<>ptr',
|
|
30
|
+
hInsertAfter: '<>ptr',
|
|
31
|
+
item: TVITEM.__struct,
|
|
16
32
|
});
|
|
17
|
-
const TVINSERTSTRUCT = struct({ hParent: 'ptr', hInsertAfter: 'ptr', item: TVITEM });
|
|
18
33
|
export const suite = {
|
|
19
34
|
name: 'ffi-struct',
|
|
20
35
|
run: (t) => {
|
|
21
36
|
const is64 = os.arch === 'x64';
|
|
22
37
|
t.section('RECT layout & roundtrip');
|
|
23
38
|
t.check('RECT.size == 16', 16, RECT.size);
|
|
24
|
-
const rbuf = RECT.
|
|
25
|
-
const r = RECT.
|
|
39
|
+
const rbuf = RECT.encode({ left: 10, top: 20, right: 30, bottom: 40 });
|
|
40
|
+
const r = RECT.decode(rbuf);
|
|
26
41
|
t.check('left', 10, r.left);
|
|
27
42
|
t.check('top', 20, r.top);
|
|
28
43
|
t.check('right', 30, r.right);
|
|
29
44
|
t.check('bottom', 40, r.bottom);
|
|
30
45
|
t.check('offsetOf left', 0, RECT.offsetOf('left'));
|
|
31
46
|
t.check('offsetOf bottom', 12, RECT.offsetOf('bottom'));
|
|
47
|
+
// decode 入参随 N 品牌化:命名 struct 收 ArrayBuffer | Ptr<其名字>,
|
|
48
|
+
// 未命名(N='')才归一 ArrayBuffer | number
|
|
49
|
+
expectType(true);
|
|
50
|
+
expectType(true);
|
|
51
|
+
if (false) {
|
|
52
|
+
// @ts-expect-error plain number is not assignable to ArrayBuffer | Ptr<'RECT'>
|
|
53
|
+
RECT.decode(123);
|
|
54
|
+
}
|
|
32
55
|
t.section('GetWindowRect fills RECT buffer');
|
|
33
|
-
const getWindowRect = bind('user32.dll', 'GetWindowRect', 'ptr ptr ->
|
|
34
|
-
const
|
|
56
|
+
const getWindowRect = bind('user32.dll', 'GetWindowRect', '<>ptr <BYTE>ptr -> i32');
|
|
57
|
+
const getWindowRectLayout = bind('user32.dll', 'GetWindowRect', '<>ptr <RECT>ptr -> i32', { RECT });
|
|
58
|
+
const getDesktopWindow = bind('user32.dll', 'GetDesktopWindow', ' -> <>ptr');
|
|
35
59
|
const hwnd = getDesktopWindow();
|
|
36
|
-
const wrect = new
|
|
60
|
+
const wrect = new PtrArrayBuffer(16);
|
|
37
61
|
const ok = getWindowRect(hwnd, wrect);
|
|
38
62
|
t.checkTrue('GetWindowRect succeeds', ok !== 0);
|
|
39
|
-
const wr = RECT.
|
|
63
|
+
const wr = RECT.decode(wrect);
|
|
40
64
|
t.checkTrue('screen RECT non-empty', wr.right > 0 && wr.bottom > 0);
|
|
65
|
+
// out 参数:encode() 新建零 buffer 返回 PtrArrayBuffer<'RECT'> → .ptr 直接喂
|
|
66
|
+
// <RECT>ptr,读回走 def.decode(实例)(ArrayBuffer 分支直读)
|
|
67
|
+
t.section('RECT.alloc() out-param → PtrArrayBuffer');
|
|
68
|
+
const out = RECT.alloc();
|
|
69
|
+
t.checkTrue('encode() exposes ArrayBuffer subclass + ptr', out instanceof ArrayBuffer && typeof out.ptr === 'number');
|
|
70
|
+
t.checkTrue('GetWindowRect(hwnd, out.ptr) succeeds', getWindowRectLayout(hwnd, out.ptr) !== 0);
|
|
71
|
+
const or = RECT.decode(out);
|
|
72
|
+
t.checkTrue('RECT.decode(encode()) decodes out-param', or.right > 0 && or.bottom > 0);
|
|
41
73
|
t.section('ptr layout matches arch');
|
|
42
74
|
t.check(`TVITEM.size (${os.arch})`, is64 ? 56 : 40, TVITEM.size);
|
|
43
75
|
t.check('TVITEM.offsetOf pszText', is64 ? 24 : 16, TVITEM.offsetOf('pszText'));
|
|
44
76
|
t.check('TVINSERTSTRUCT.size', is64 ? 72 : 48, TVINSERTSTRUCT.size);
|
|
45
77
|
t.check('TVINSERTSTRUCT.offsetOf item', is64 ? 16 : 8, TVINSERTSTRUCT.offsetOf('item'));
|
|
46
78
|
t.section('nested struct roundtrip');
|
|
47
|
-
const ins = TVINSERTSTRUCT.
|
|
79
|
+
const ins = TVINSERTSTRUCT.encode({
|
|
48
80
|
hParent: 0x11111111,
|
|
49
81
|
hInsertAfter: 0x22222222,
|
|
50
82
|
item: {
|
|
@@ -53,108 +85,345 @@ export const suite = {
|
|
|
53
85
|
iImage: 0, iSelectedImage: 0, cChildren: 5, lParam: 0x55555555,
|
|
54
86
|
},
|
|
55
87
|
});
|
|
56
|
-
const got = TVINSERTSTRUCT.
|
|
88
|
+
const got = TVINSERTSTRUCT.decode(ins);
|
|
57
89
|
t.check('nested hParent', 0x11111111, got.hParent);
|
|
58
90
|
t.check('nested hInsertAfter', 0x22222222, got.hInsertAfter);
|
|
59
91
|
t.check('nested cChildren', 5, got.item.cChildren);
|
|
60
92
|
t.check('nested ptr pszText', 0x44444444, got.item.pszText);
|
|
61
|
-
t.section('
|
|
62
|
-
const M = struct(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
t.check(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
t.check('
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
t.check('
|
|
78
|
-
t.
|
|
79
|
-
|
|
93
|
+
t.section('mixed scalar layout (i32/u32/i16/<>ptr;别名机制已移除)');
|
|
94
|
+
const M = struct('M', {
|
|
95
|
+
n: 'i32',
|
|
96
|
+
d: 'u32',
|
|
97
|
+
w: '<>ptr',
|
|
98
|
+
s: 'i16',
|
|
99
|
+
q: '<>ptr',
|
|
100
|
+
});
|
|
101
|
+
t.check(`mixed size (${os.arch})`, is64 ? 32 : 20, M.size);
|
|
102
|
+
const mb = M.encode({ n: -5, d: 0xFFFFFFFF, w: 0xAABBCCDD, s: -7, q: 0x11223344 });
|
|
103
|
+
const m = M.decode(mb);
|
|
104
|
+
t.check('i32 -5', -5, m.n);
|
|
105
|
+
t.check('u32 0xFFFFFFFF', 0xFFFFFFFF, m.d);
|
|
106
|
+
// ptr 位有符号读(对齐 C 版 JS_NewInt64):0xAABBCCDD 在 ia32 按 4 字节
|
|
107
|
+
// 存取 → 符号扩展读成负;x64 8 字节高位 0 → 仍为正
|
|
108
|
+
t.check('<>ptr 符号读', is64 ? 0xAABBCCDD : -1430532899, m.w);
|
|
109
|
+
t.check('i16 -7', -7, m.s);
|
|
110
|
+
t.check('<>ptr 0x11223344', 0x11223344, m.q);
|
|
111
|
+
// 别名机制已移除:typedef 名塌出词汇表 —— struct 字段值只收规范 token
|
|
112
|
+
// (仅类型层断言,包成函数体不执行,避免运行时 fail-loud 崩掉 suite)
|
|
113
|
+
// @ts-expect-error typedef 别名 'DWORD' 已移除
|
|
114
|
+
void (() => struct('XD', { d: 'DWORD' }));
|
|
115
|
+
t.section('array sugar + trailing bitfield(数组糖与尾随位域单元并存)');
|
|
116
|
+
const AS = struct('AS', {
|
|
117
|
+
v: 'u32[4]',
|
|
118
|
+
f: 'u32:3',
|
|
119
|
+
});
|
|
120
|
+
t.check('size == 20 (16 + 位域单元)', 20, AS.size);
|
|
121
|
+
t.check('f offset == 16', 16, AS.offsetOf('f'));
|
|
122
|
+
const asd = AS.decode(AS.encode({ v: [1, 2, 3, 4], f: 5 }));
|
|
123
|
+
t.check('u32[4][1]', 2, asd.v[1]);
|
|
124
|
+
t.check('u32:3', 5, asd.f);
|
|
125
|
+
expectType();
|
|
126
|
+
t.section('union() entry roundtrip');
|
|
127
|
+
const U = union({ a: 'u8', b: 'u32' });
|
|
128
|
+
t.check('union size == 4', 4, U.size);
|
|
129
|
+
t.check('union offsetOf a', 0, U.offsetOf('a'));
|
|
130
|
+
t.check('union offsetOf b', 0, U.offsetOf('b'));
|
|
131
|
+
const ud = U.decode(U.encode({ a: 0, b: 0x01020304 }));
|
|
132
|
+
t.check('union a 与 b 同偏移(低字节别名)', 0x04, ud.a);
|
|
133
|
+
t.check('union b', 0x01020304, ud.b);
|
|
134
|
+
t.section('union 位域完全别名 roundtrip(mingw 实测语义)');
|
|
135
|
+
{
|
|
136
|
+
const UB = union({ a: 'u32:1', b: 'u32:1' });
|
|
137
|
+
t.check('union bitfield size == 4', 4, UB.size);
|
|
138
|
+
const ud1 = UB.decode(UB.encode({ a: 1, b: 1 }));
|
|
139
|
+
t.check('a=1,b=1 roundtrip a', 1, ud1.a);
|
|
140
|
+
t.check('a=1,b=1 roundtrip b', 1, ud1.b);
|
|
141
|
+
// 同位别名:字段按声明序写入,后写字段覆盖先写字段
|
|
142
|
+
const ud2 = UB.decode(UB.encode({ a: 1, b: 0 }));
|
|
143
|
+
t.check('别名覆盖:b=0 后写 → a 读 0', 0, ud2.a);
|
|
144
|
+
t.check('别名覆盖:b == 0', 0, ud2.b);
|
|
145
|
+
// 溢出形状(30+4>32):union 每成员回 offset 0,曾开新单元越出 size
|
|
146
|
+
const OF = union({ a: 'u32:30', b: 'u32:4' });
|
|
147
|
+
t.check('overflow union size == 4', 4, OF.size);
|
|
148
|
+
const od1 = OF.decode(OF.encode({ a: 0x3FFFFFFF, b: 0xF }));
|
|
149
|
+
t.check('overflow roundtrip a', 0x3FFFFFFF, od1.a);
|
|
150
|
+
t.check('overflow roundtrip b', 0xF, od1.b);
|
|
151
|
+
const od2 = OF.decode(OF.encode({ a: 0x3FFFFFFF, b: 0 }));
|
|
152
|
+
t.check('overflow 别名覆盖 → a 低 4 位被清', 0x3FFFFFF0, od2.a);
|
|
153
|
+
// 位域 + 普通成员混合:b 曾被 flushUnit 推到 offset 4(越出 size 4)
|
|
154
|
+
const MX = union({ a: 'u32:1', b: 'u8' });
|
|
155
|
+
t.check('mixed union size == 4', 4, MX.size);
|
|
156
|
+
t.check('mixed offsetOf b == 0(曾为 4)', 0, MX.offsetOf('b'));
|
|
157
|
+
const md = MX.decode(MX.encode({ a: 1, b: 0x54 }));
|
|
158
|
+
t.check('mixed: b 覆盖 a 所在字节 → a 读 0', 0, md.a);
|
|
159
|
+
t.check('mixed: b == 0x54', 0x54, md.b);
|
|
160
|
+
// 嵌套回归:struct 内嵌该 union(raw '#' 形式;union() 装饰对象不能嵌套,
|
|
161
|
+
// 嵌套 union 的解码类型推导会 TS2589,沿用旧行例 as any——本条是运行时断言),
|
|
162
|
+
// tail 不被 union 内错位写污染
|
|
163
|
+
const nestedU = { '#': 'union', a: 'u32:1', b: 'u8' };
|
|
164
|
+
const NS = struct({ tag: 'u32', u: nestedU, tail: 'u16' });
|
|
165
|
+
t.check('nested size == 12', 12, NS.size);
|
|
166
|
+
const nd = NS.decode(NS.encode({ tag: 0xAABBCCDD, u: { a: 1, b: 0x54 }, tail: 0x1234 }));
|
|
167
|
+
t.check('nested tag', 0xAABBCCDD, nd.tag);
|
|
168
|
+
t.check('nested u.b == 0x54', 0x54, nd.u.b);
|
|
169
|
+
t.check('nested tail 不被污染', 0x1234, nd.tail);
|
|
170
|
+
}
|
|
171
|
+
t.section('struct() entry: pack 选项 + 键尾 @N(alignas)');
|
|
172
|
+
const P = struct('P', { a: 'u8', b: 'u32', c: 'u8' }, { pack: 1 });
|
|
173
|
+
t.check('pack(1) size == 6', 6, P.size);
|
|
174
|
+
t.check('pack(1) offsetOf b', 1, P.offsetOf('b'));
|
|
175
|
+
const AL = struct('AL', { a: 'u8', 'b@8': 'u32' });
|
|
176
|
+
t.check('@N offsetOf a', 0, AL.offsetOf('a'));
|
|
177
|
+
t.check('@N offsetOf b(字段名已剥离 @N)', 8, AL.offsetOf('b'));
|
|
178
|
+
t.section('utf-16le string[8] roundtrip & truncation');
|
|
179
|
+
const W = struct('W', { name: 'u16[8]@utf-16le' });
|
|
180
|
+
t.check('utf16[8].size == 16', 16, W.size);
|
|
181
|
+
const wb = W.encode({ name: 'hello' });
|
|
182
|
+
t.check('read back "hello"', 'hello', W.decode(wb).name);
|
|
183
|
+
const lb = W.encode({ name: 'a very long string over' });
|
|
184
|
+
t.check('utf16[8]: payload 7 + NUL', 'a very ', W.decode(lb).name);
|
|
185
|
+
t.check('utf16 terminator[14..15] == 0', 0, new DataView(lb).getUint16(14, true));
|
|
186
|
+
t.section("utf-8 string[8] roundtrip & truncation");
|
|
187
|
+
const C = struct('C', { name: 'u8[8]@utf-8' });
|
|
188
|
+
t.check('char[8].size == 8', 8, C.size);
|
|
189
|
+
const cb = C.encode({ name: 'hi' });
|
|
190
|
+
t.check('read back "hi"', 'hi', C.decode(cb).name);
|
|
191
|
+
t.check('NUL at [2]', 0, new DataView(cb).getUint8(2));
|
|
192
|
+
const ctrunc = C.encode({ name: '1234567890' });
|
|
193
|
+
t.check('char[8]: payload 7 + NUL', '1234567', C.decode(ctrunc).name);
|
|
194
|
+
t.check('char terminator[7] == 0', 0, new DataView(ctrunc).getUint8(7));
|
|
195
|
+
t.section('string sugar: 布局与解释正交(错配 roundtrip)');
|
|
196
|
+
const XS = struct('XS', { buf: 'u8[8]@utf-16le' });
|
|
197
|
+
t.check('u8[8]@utf-16le size == 8(encoding 不参与布局)', 8, XS.size);
|
|
198
|
+
const xsd = XS.decode(XS.encode({ buf: 'AB' }));
|
|
199
|
+
t.check('8 字节按 utf-16le 解出 "AB"', 'AB', xsd.buf);
|
|
200
|
+
t.section('string sugar as array element');
|
|
201
|
+
const SA = struct('SA', { rows: { '#': 'array', element: 'u16[4]@utf-16le', length: 3 } });
|
|
202
|
+
t.check('string[4]×3 size == 24', 24, SA.size);
|
|
203
|
+
const sad = SA.decode(SA.encode({ rows: ['ab', 'cd', 'ef'] }));
|
|
204
|
+
t.check('rows[0]', 'ab', sad.rows[0]);
|
|
205
|
+
t.check('rows[1]', 'cd', sad.rows[1]);
|
|
206
|
+
expectType();
|
|
207
|
+
t.section('键侧 alignas @N 与值侧 encoding @ 并存');
|
|
208
|
+
const KV = struct('KV', { a: 'u8', 's@8': 'u16[4]@utf-8' });
|
|
209
|
+
t.check('offsetOf a', 0, KV.offsetOf('a'));
|
|
210
|
+
t.check('offsetOf s(键尾 @8 生效)', 8, KV.offsetOf('s'));
|
|
211
|
+
t.check('KV size == 16', 16, KV.size);
|
|
212
|
+
t.section('numeric arrays roundtrip (CArray)');
|
|
213
|
+
const A = struct('A', {
|
|
214
|
+
v: 'u16[4]',
|
|
215
|
+
k: 'i32[2]',
|
|
216
|
+
});
|
|
80
217
|
t.check('size == 16', 16, A.size);
|
|
81
|
-
const ab = A.
|
|
82
|
-
const ad = A.
|
|
218
|
+
const ab = A.encode({ v: [1, 2, 3, 4], k: [-1, 300000] });
|
|
219
|
+
const ad = A.decode(ab);
|
|
83
220
|
t.check('u16[0]', 1, ad.v[0]);
|
|
84
221
|
t.check('u16[3]', 4, ad.v[3]);
|
|
85
222
|
t.check('i32[0] -1', -1, ad.k[0]);
|
|
86
223
|
t.check('i32[1]', 300000, ad.k[1]);
|
|
224
|
+
t.section('T[1] stays an array (no scalar degradation)');
|
|
225
|
+
const ONE = struct('ONE', { v: 'u16[1]' });
|
|
226
|
+
t.check('size == 2', 2, ONE.size);
|
|
227
|
+
const one = ONE.decode(ONE.encode({ v: [42] }));
|
|
228
|
+
t.check('length 1', 1, one.v.length);
|
|
229
|
+
t.check('[0]', 42, one.v[0]);
|
|
230
|
+
t.section('array of struct roundtrip (stride = child size)');
|
|
231
|
+
const PT = struct('PT', {
|
|
232
|
+
x: 'i32',
|
|
233
|
+
y: 'i32',
|
|
234
|
+
});
|
|
235
|
+
const POLY = struct('POLY', {
|
|
236
|
+
count: 'u32',
|
|
237
|
+
pts: { '#': 'array', element: PT.__struct, length: 3 },
|
|
238
|
+
});
|
|
239
|
+
t.check('POLY.size == 28', 28, POLY.size);
|
|
240
|
+
t.check('POLY.offsetOf pts', 4, POLY.offsetOf('pts'));
|
|
241
|
+
const poly = POLY.decode(POLY.encode({ count: 3, pts: [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }] }));
|
|
242
|
+
t.check('count', 3, poly.count);
|
|
243
|
+
t.check('pts[0].x', 1, poly.pts[0].x);
|
|
244
|
+
t.check('pts[2].y', 6, poly.pts[2].y);
|
|
245
|
+
// 编译期断言:CType IR 推导 — string / 定长数组元组 / 嵌套 struct 递归
|
|
246
|
+
expectType();
|
|
247
|
+
expectType();
|
|
248
|
+
expectType();
|
|
249
|
+
expectType();
|
|
250
|
+
expectType();
|
|
251
|
+
expectType();
|
|
252
|
+
let rejected = false;
|
|
253
|
+
try {
|
|
254
|
+
struct({ v: 'i32[0]' });
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
rejected = true;
|
|
258
|
+
}
|
|
259
|
+
t.check('array length 0 rejected', true, rejected);
|
|
260
|
+
let rejectedBare = false;
|
|
261
|
+
try {
|
|
262
|
+
struct({ p: 'ptr' });
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
rejectedBare = true;
|
|
266
|
+
}
|
|
267
|
+
t.check('bare "ptr" member rejected', true, rejectedBare);
|
|
268
|
+
// '#' 必填:字段值漏写 '#'(裸嵌套 map)不再默认 struct,直接 throw
|
|
269
|
+
let rejectedMap = false;
|
|
270
|
+
try {
|
|
271
|
+
struct({ outer: { inner: 'u32' } });
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
rejectedMap = true;
|
|
275
|
+
}
|
|
276
|
+
t.check('bare nested map rejected(漏写 #)', true, rejectedMap);
|
|
277
|
+
// 顶层 '#' 由构造器写入:字段表混入的 '#' 指令键被剔除(运行时兜底,类型层已禁)
|
|
278
|
+
const H = struct({ '#': 'union', a: 'u8', b: 'u32' });
|
|
279
|
+
t.check('top-level # stripped(仍按函数名 struct 布局)', 8, H.size);
|
|
280
|
+
t.section("pointer member '<>ptr' raw vs '<T>ptr' branded");
|
|
281
|
+
const PTR = struct('PTR', {
|
|
282
|
+
raw: '<>ptr',
|
|
283
|
+
r: '<RECT>ptr',
|
|
284
|
+
tag: 'u32',
|
|
285
|
+
});
|
|
286
|
+
t.check('PTR.size', is64 ? 24 : 12, PTR.size);
|
|
287
|
+
t.check('offsetOf raw', 0, PTR.offsetOf('raw'));
|
|
288
|
+
t.check('offsetOf r', is64 ? 8 : 4, PTR.offsetOf('r'));
|
|
289
|
+
t.check('offsetOf tag', is64 ? 16 : 8, PTR.offsetOf('tag'));
|
|
290
|
+
const rawOut = RECT.alloc();
|
|
291
|
+
const pd = PTR.decode(PTR.encode({ raw: rawOut.ptr, r: rawOut.ptr, tag: 7 }));
|
|
292
|
+
t.check('raw roundtrip', rawOut.ptr, pd.raw);
|
|
293
|
+
t.check('r roundtrip', rawOut.ptr, pd.r);
|
|
294
|
+
t.check('tag', 7, pd.tag);
|
|
295
|
+
// 编译期:'<>ptr' → MaybePtr<''>(NULL|number,与 number 双向可赋值,NULL 即 0 保真);
|
|
296
|
+
// '<RECT>ptr' → MaybePtr<'RECT'>(NULL|Ptr,0 保真,传参前收窄)。
|
|
297
|
+
expectType();
|
|
298
|
+
expectType();
|
|
299
|
+
expectType();
|
|
87
300
|
t.section('f32/f64 layout (MSVC: f@0, double@8)');
|
|
88
|
-
const FL = struct(
|
|
301
|
+
const FL = struct('FL', {
|
|
302
|
+
f: 'f32', // float
|
|
303
|
+
d: 'f64', // double
|
|
304
|
+
});
|
|
89
305
|
t.check('size == 16 (f@0, double aligned @8)', 16, FL.size);
|
|
90
|
-
const fb = FL.
|
|
91
|
-
const fr = FL.
|
|
306
|
+
const fb = FL.encode({ f: 1.5, d: -2.25 });
|
|
307
|
+
const fr = FL.decode(fb);
|
|
92
308
|
t.check('float', 1.5, fr.f);
|
|
93
309
|
t.check('double', -2.25, fr.d);
|
|
94
310
|
t.section('comctl dialog structs track 32/64-bit ABI');
|
|
95
|
-
const TCITEMW = struct({
|
|
96
|
-
mask: 'u32',
|
|
97
|
-
|
|
311
|
+
const TCITEMW = struct('TCITEMW', {
|
|
312
|
+
mask: 'u32',
|
|
313
|
+
dwState: 'u32',
|
|
314
|
+
dwStateMask: 'u32',
|
|
315
|
+
pszText: '<>ptr',
|
|
316
|
+
cchTextMax: 'i32',
|
|
317
|
+
iImage: 'i32',
|
|
318
|
+
lParam: '<>ptr',
|
|
98
319
|
});
|
|
99
320
|
t.check('TCITEMW.size', is64 ? 40 : 28, TCITEMW.size);
|
|
100
321
|
t.check('TCITEMW.offsetOf pszText', is64 ? 16 : 12, TCITEMW.offsetOf('pszText'));
|
|
101
322
|
t.check('TCITEMW.offsetOf cchTextMax', is64 ? 24 : 16, TCITEMW.offsetOf('cchTextMax'));
|
|
102
|
-
const TTTOOLINFOW = struct({
|
|
103
|
-
cbSize: 'u32',
|
|
104
|
-
|
|
105
|
-
|
|
323
|
+
const TTTOOLINFOW = struct('TTTOOLINFOW', {
|
|
324
|
+
cbSize: 'u32',
|
|
325
|
+
uFlags: 'u32',
|
|
326
|
+
hwnd: '<>ptr',
|
|
327
|
+
uId: '<>ptr',
|
|
328
|
+
rect: 'i32[4]',
|
|
329
|
+
hinst: '<>ptr',
|
|
330
|
+
lpszText: '<>ptr',
|
|
331
|
+
lParam: '<>ptr',
|
|
332
|
+
lpReserved: '<>ptr', // WinXP+ 追加
|
|
106
333
|
});
|
|
107
334
|
t.check('TTTOOLINFOW.size', is64 ? 72 : 48, TTTOOLINFOW.size);
|
|
108
335
|
t.check('TTTOOLINFOW.offsetOf lpszText', is64 ? 48 : 36, TTTOOLINFOW.offsetOf('lpszText'));
|
|
109
|
-
const OPENFILENAMEW = struct({
|
|
110
|
-
lStructSize: 'u32',
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
336
|
+
const OPENFILENAMEW = struct('OPENFILENAMEW', {
|
|
337
|
+
lStructSize: 'u32',
|
|
338
|
+
hwndOwner: '<>ptr',
|
|
339
|
+
hInstance: '<>ptr',
|
|
340
|
+
lpstrFilter: '<>ptr',
|
|
341
|
+
lpstrCustomFilter: '<>ptr',
|
|
342
|
+
nMaxCustFilter: 'u32',
|
|
343
|
+
nFilterIndex: 'u32',
|
|
344
|
+
lpstrFile: '<>ptr',
|
|
345
|
+
nMaxFile: 'u32',
|
|
346
|
+
lpstrFileTitle: '<>ptr',
|
|
347
|
+
nMaxFileTitle: 'u32',
|
|
348
|
+
lpstrInitialDir: '<>ptr',
|
|
349
|
+
lpstrTitle: '<>ptr',
|
|
350
|
+
Flags: 'u32',
|
|
351
|
+
nFileOffset: 'u16',
|
|
352
|
+
nFileExtension: 'u16',
|
|
353
|
+
lpstrDefExt: '<>ptr',
|
|
354
|
+
lCustData: '<>ptr',
|
|
355
|
+
lpfnHook: '<>ptr',
|
|
356
|
+
lpTemplateName: '<>ptr',
|
|
357
|
+
pvReserved: '<>ptr',
|
|
358
|
+
dwReserved: 'u32',
|
|
359
|
+
FlagsEx: 'u32', // Win2000+ 追加
|
|
117
360
|
});
|
|
118
361
|
t.check('OPENFILENAMEW.size', is64 ? 152 : 88, OPENFILENAMEW.size);
|
|
119
362
|
t.check('OPENFILENAMEW.offsetOf lpstrFile', is64 ? 48 : 28, OPENFILENAMEW.offsetOf('lpstrFile'));
|
|
120
363
|
t.check('OPENFILENAMEW.offsetOf Flags', is64 ? 96 : 52, OPENFILENAMEW.offsetOf('Flags'));
|
|
121
364
|
t.check('OPENFILENAMEW.offsetOf lpstrTitle', is64 ? 88 : 48, OPENFILENAMEW.offsetOf('lpstrTitle'));
|
|
122
|
-
const BROWSEINFOW = struct({
|
|
123
|
-
hwndOwner: 'ptr',
|
|
124
|
-
|
|
365
|
+
const BROWSEINFOW = struct('BROWSEINFOW', {
|
|
366
|
+
hwndOwner: '<>ptr',
|
|
367
|
+
pidlRoot: '<>ptr',
|
|
368
|
+
pszDisplayName: '<>ptr',
|
|
369
|
+
lpszTitle: '<>ptr',
|
|
370
|
+
ulFlags: 'u32',
|
|
371
|
+
lpfn: '<>ptr',
|
|
372
|
+
lParam: '<>ptr',
|
|
373
|
+
iImage: 'i32',
|
|
125
374
|
});
|
|
126
375
|
t.check('BROWSEINFOW.size', is64 ? 64 : 32, BROWSEINFOW.size);
|
|
127
376
|
t.check('BROWSEINFOW.offsetOf lpszTitle', is64 ? 24 : 12, BROWSEINFOW.offsetOf('lpszTitle'));
|
|
128
377
|
t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'));
|
|
129
378
|
t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)');
|
|
130
|
-
const NMHDR = struct(
|
|
379
|
+
const NMHDR = struct('NMHDR', {
|
|
380
|
+
hwndFrom: '<>ptr',
|
|
381
|
+
idFrom: '<>ptr',
|
|
382
|
+
code: 'i32',
|
|
383
|
+
});
|
|
131
384
|
t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size);
|
|
132
385
|
t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'));
|
|
133
386
|
t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)');
|
|
134
|
-
const NMCUSTOMDRAW = struct({
|
|
135
|
-
hdr: NMHDR,
|
|
136
|
-
dwDrawStage: 'u32',
|
|
137
|
-
|
|
387
|
+
const NMCUSTOMDRAW = struct('NMCUSTOMDRAW', {
|
|
388
|
+
hdr: NMHDR.__struct,
|
|
389
|
+
dwDrawStage: 'u32',
|
|
390
|
+
hdc: '<>ptr',
|
|
391
|
+
rc: 'i32[4]',
|
|
392
|
+
dwItemSpec: '<>ptr',
|
|
393
|
+
uItemState: 'u32',
|
|
394
|
+
lItemlParam: '<>ptr',
|
|
138
395
|
});
|
|
139
396
|
t.check('NMCUSTOMDRAW.size', is64 ? 80 : 48, NMCUSTOMDRAW.size);
|
|
140
397
|
t.check('NMCUSTOMDRAW.offsetOf dwDrawStage', is64 ? 24 : 12, NMCUSTOMDRAW.offsetOf('dwDrawStage'));
|
|
141
398
|
t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'));
|
|
142
399
|
t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'));
|
|
143
|
-
const NMLVCUSTOMDRAW = struct({
|
|
144
|
-
hdr: NMHDR,
|
|
145
|
-
dwDrawStage: 'u32',
|
|
146
|
-
|
|
147
|
-
|
|
400
|
+
const NMLVCUSTOMDRAW = struct('NMLVCUSTOMDRAW', {
|
|
401
|
+
hdr: NMHDR.__struct,
|
|
402
|
+
dwDrawStage: 'u32',
|
|
403
|
+
hdc: '<>ptr',
|
|
404
|
+
rc: 'i32[4]',
|
|
405
|
+
dwItemSpec: '<>ptr',
|
|
406
|
+
uItemState: 'u32',
|
|
407
|
+
lItemlParam: '<>ptr',
|
|
408
|
+
clrText: 'u32',
|
|
409
|
+
clrTextBk: 'u32',
|
|
410
|
+
iSubItem: 'i32',
|
|
411
|
+
dwItemType: 'u32',
|
|
148
412
|
});
|
|
149
413
|
t.check('NMLVCUSTOMDRAW.size', is64 ? 96 : 64, NMLVCUSTOMDRAW.size);
|
|
150
414
|
t.check('NMLVCUSTOMDRAW.offsetOf clrText', is64 ? 80 : 48, NMLVCUSTOMDRAW.offsetOf('clrText'));
|
|
151
415
|
t.check('NMLVCUSTOMDRAW.offsetOf clrTextBk', is64 ? 84 : 52, NMLVCUSTOMDRAW.offsetOf('clrTextBk'));
|
|
152
416
|
t.check('NMLVCUSTOMDRAW.offsetOf iSubItem', is64 ? 88 : 56, NMLVCUSTOMDRAW.offsetOf('iSubItem'));
|
|
153
417
|
t.section('NMLISTVIEW / NMITEMACTIVATE');
|
|
154
|
-
const NMLISTVIEW = struct({
|
|
155
|
-
hdr: NMHDR,
|
|
156
|
-
iItem: 'i32',
|
|
157
|
-
|
|
418
|
+
const NMLISTVIEW = struct('NMLISTVIEW', {
|
|
419
|
+
hdr: NMHDR.__struct,
|
|
420
|
+
iItem: 'i32',
|
|
421
|
+
iSubItem: 'i32',
|
|
422
|
+
uNewState: 'u32',
|
|
423
|
+
uOldState: 'u32',
|
|
424
|
+
uChanged: 'u32',
|
|
425
|
+
ptAction: 'i32[2]',
|
|
426
|
+
lParam: '<>ptr',
|
|
158
427
|
});
|
|
159
428
|
t.check('NMLISTVIEW.size', is64 ? 64 : 44, NMLISTVIEW.size);
|
|
160
429
|
t.check('NMLISTVIEW.offsetOf iItem', is64 ? 24 : 12, NMLISTVIEW.offsetOf('iItem'));
|
|
@@ -162,40 +431,315 @@ export const suite = {
|
|
|
162
431
|
t.check('NMLISTVIEW.offsetOf uNewState', is64 ? 32 : 20, NMLISTVIEW.offsetOf('uNewState'));
|
|
163
432
|
t.check('NMLISTVIEW.offsetOf uOldState', is64 ? 36 : 24, NMLISTVIEW.offsetOf('uOldState'));
|
|
164
433
|
t.section('LVITEMW / LVCOLUMNW');
|
|
165
|
-
const LVITEMW = struct({
|
|
166
|
-
mask: 'u32',
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
434
|
+
const LVITEMW = struct('LVITEMW', {
|
|
435
|
+
mask: 'u32',
|
|
436
|
+
iItem: 'i32',
|
|
437
|
+
iSubItem: 'i32',
|
|
438
|
+
state: 'u32',
|
|
439
|
+
stateMask: 'u32',
|
|
440
|
+
pszText: '<>ptr',
|
|
441
|
+
cchTextMax: 'i32',
|
|
442
|
+
iImage: 'i32',
|
|
443
|
+
lParam: '<>ptr',
|
|
444
|
+
iIndent: 'i32',
|
|
445
|
+
iGroupId: 'i32',
|
|
446
|
+
cColumns: 'u32',
|
|
447
|
+
puColumns: '<>ptr',
|
|
448
|
+
piColFmt: '<>ptr',
|
|
449
|
+
iGroup: 'i32',
|
|
171
450
|
});
|
|
172
451
|
t.check('LVITEMW.size', is64 ? 88 : 60, LVITEMW.size);
|
|
173
452
|
t.check('LVITEMW.offsetOf iItem', 4, LVITEMW.offsetOf('iItem'));
|
|
174
453
|
t.check('LVITEMW.offsetOf pszText', is64 ? 24 : 20, LVITEMW.offsetOf('pszText'));
|
|
175
454
|
t.check('LVITEMW.offsetOf iImage', is64 ? 36 : 28, LVITEMW.offsetOf('iImage'));
|
|
176
455
|
t.check('LVITEMW.offsetOf lParam', is64 ? 40 : 32, LVITEMW.offsetOf('lParam'));
|
|
177
|
-
const LVCOLUMNW = struct({
|
|
178
|
-
mask: 'u32',
|
|
179
|
-
|
|
180
|
-
|
|
456
|
+
const LVCOLUMNW = struct('LVCOLUMNW', {
|
|
457
|
+
mask: 'u32',
|
|
458
|
+
fmt: 'i32',
|
|
459
|
+
cx: 'i32',
|
|
460
|
+
pszText: '<>ptr',
|
|
461
|
+
cchTextMax: 'i32',
|
|
462
|
+
iSubItem: 'i32',
|
|
463
|
+
iImage: 'i32',
|
|
464
|
+
iOrder: 'i32',
|
|
465
|
+
cxMin: 'i32',
|
|
466
|
+
cxDefault: 'i32',
|
|
467
|
+
cxIdeal: 'i32',
|
|
181
468
|
});
|
|
182
469
|
t.check('LVCOLUMNW.size', is64 ? 56 : 44, LVCOLUMNW.size);
|
|
183
470
|
t.check('LVCOLUMNW.offsetOf pszText', is64 ? 16 : 12, LVCOLUMNW.offsetOf('pszText'));
|
|
184
471
|
t.check('LVCOLUMNW.offsetOf iSubItem', is64 ? 28 : 20, LVCOLUMNW.offsetOf('iSubItem'));
|
|
185
472
|
t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'));
|
|
186
473
|
t.section('NMDATETIMECHANGE');
|
|
187
|
-
const SYSTEMTIME = struct({
|
|
188
|
-
wYear: 'u16',
|
|
189
|
-
|
|
474
|
+
const SYSTEMTIME = struct('SYSTEMTIME', {
|
|
475
|
+
wYear: 'u16',
|
|
476
|
+
wMonth: 'u16',
|
|
477
|
+
wDayOfWeek: 'u16',
|
|
478
|
+
wDay: 'u16',
|
|
479
|
+
wHour: 'u16',
|
|
480
|
+
wMinute: 'u16',
|
|
481
|
+
wSecond: 'u16',
|
|
482
|
+
wMilliseconds: 'u16',
|
|
483
|
+
});
|
|
484
|
+
const NMDATETIMECHANGE = struct('NMDATETIMECHANGE', {
|
|
485
|
+
hdr: NMHDR.__struct,
|
|
486
|
+
dwFlags: 'u32',
|
|
487
|
+
st: SYSTEMTIME.__struct,
|
|
190
488
|
});
|
|
191
|
-
const NMDATETIMECHANGE = struct({ hdr: NMHDR, dwFlags: 'u32', st: SYSTEMTIME });
|
|
192
489
|
t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size);
|
|
193
490
|
t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'));
|
|
194
491
|
t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'));
|
|
195
492
|
t.section('NMLINK szUrl offset');
|
|
196
|
-
const LITEM = struct(
|
|
197
|
-
|
|
493
|
+
const LITEM = struct('LITEM', {
|
|
494
|
+
mask: 'u32',
|
|
495
|
+
iLink: 'i32',
|
|
496
|
+
state: 'u32',
|
|
497
|
+
stateMask: 'u32',
|
|
498
|
+
szID: 'u16[48]@utf-16le',
|
|
499
|
+
});
|
|
500
|
+
const NMLINK = struct('NMLINK', {
|
|
501
|
+
hdr: NMHDR.__struct,
|
|
502
|
+
item: LITEM.__struct,
|
|
503
|
+
szUrl: 'u16[2084]@utf-16le',
|
|
504
|
+
});
|
|
198
505
|
t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'));
|
|
199
506
|
t.check('NMLINK.offsetOf szUrl', is64 ? 136 : 124, NMLINK.offsetOf('szUrl'));
|
|
507
|
+
// === 位域读/写往返 ===
|
|
508
|
+
// 布局 ground truth 在 test_ffi_struct_layout.ts(mingw 交叉实测),这里只测读写。
|
|
509
|
+
t.section('bitfield roundtrip: LSB→MSB 打包 + 读改写隔离');
|
|
510
|
+
{
|
|
511
|
+
// 字段按序写入同一 buffer:写 d 时单元里已有 a/b/c 的位,
|
|
512
|
+
// 若 writeBitfield 不做读改写(直接 val<<bit)会把 a/b/c 清零。
|
|
513
|
+
const BF = struct('BF', {
|
|
514
|
+
a: 'u32:3',
|
|
515
|
+
b: 'u32:5',
|
|
516
|
+
c: 'u32:4',
|
|
517
|
+
d: 'u32:10', // 3+5+4+10=22 <= 32
|
|
518
|
+
tail: 'u32',
|
|
519
|
+
});
|
|
520
|
+
t.check('BF.size == 8', 8, BF.size);
|
|
521
|
+
const bd = BF.decode(BF.encode({ a: 5, b: 31, c: 15, d: 1023, tail: 0xDEAD }));
|
|
522
|
+
t.check('a (LSB)', 5, bd.a);
|
|
523
|
+
t.check('b', 31, bd.b);
|
|
524
|
+
t.check('c', 15, bd.c);
|
|
525
|
+
t.check('d (max 10-bit)', 1023, bd.d);
|
|
526
|
+
t.check('tail survives bitfield unit', 0xDEAD, bd.tail);
|
|
527
|
+
// 位域写不破坏同单元的邻居:a=5 后写 b=31,a 仍为 5
|
|
528
|
+
t.check('a survives b write', 5, bd.a);
|
|
529
|
+
}
|
|
530
|
+
t.section('bitfield roundtrip: signed sign extension');
|
|
531
|
+
{
|
|
532
|
+
const SB = struct('SB', {
|
|
533
|
+
s: 'i32:8',
|
|
534
|
+
u: 'u32:4',
|
|
535
|
+
});
|
|
536
|
+
t.check('SB.size == 4 (同宽不分签别,共单元)', 4, SB.size);
|
|
537
|
+
const sbd = SB.decode(SB.encode({ s: -5, u: 9 }));
|
|
538
|
+
t.check('signed -5', -5, sbd.s);
|
|
539
|
+
t.check('unsigned 9', 9, sbd.u);
|
|
540
|
+
t.check('signed -1', -1, SB.decode(SB.encode({ s: -1, u: 0 })).s);
|
|
541
|
+
t.check('signed 127 (min int8)', 127, SB.decode(SB.encode({ s: 127, u: 0 })).s);
|
|
542
|
+
t.check('signed 128 → -128', -128, SB.decode(SB.encode({ s: 128, u: 0 })).s);
|
|
543
|
+
}
|
|
544
|
+
t.section('bitfield roundtrip: overflow starts a new unit');
|
|
545
|
+
{
|
|
546
|
+
const OV = struct('OV', {
|
|
547
|
+
a: 'u32:30',
|
|
548
|
+
b: 'u32:4', // 30+4=34 > 32 → 新单元,剩余 2 位废弃
|
|
549
|
+
c: 'u32',
|
|
550
|
+
});
|
|
551
|
+
t.check('OV.size == 12', 12, OV.size);
|
|
552
|
+
t.check('OV.offsetOf b (new unit)', 4, OV.offsetOf('b'));
|
|
553
|
+
t.check('OV.offsetOf c', 8, OV.offsetOf('c'));
|
|
554
|
+
const ovd = OV.decode(OV.encode({ a: 0x3FFFFFFF, b: 15, c: 7 }));
|
|
555
|
+
t.check('OV a (max 30-bit)', 0x3FFFFFFF, ovd.a);
|
|
556
|
+
t.check('OV b', 15, ovd.b);
|
|
557
|
+
t.check('OV c', 7, ovd.c);
|
|
558
|
+
}
|
|
559
|
+
t.section('bitfield: DCB roundtrip (Windows serial port config)');
|
|
560
|
+
{
|
|
561
|
+
const DCB = struct('DCB', {
|
|
562
|
+
DCBlength: 'u32',
|
|
563
|
+
BaudRate: 'u32',
|
|
564
|
+
fBinary: 'u32:1',
|
|
565
|
+
fParity: 'u32:1',
|
|
566
|
+
fOutxCtsFlow: 'u32:1',
|
|
567
|
+
fOutxDsrFlow: 'u32:1',
|
|
568
|
+
fDtrControl: 'u32:2',
|
|
569
|
+
fDsrSensitivity: 'u32:1',
|
|
570
|
+
fTXContinueOnXoff: 'u32:1',
|
|
571
|
+
fOutX: 'u32:1',
|
|
572
|
+
fInX: 'u32:1',
|
|
573
|
+
fErrorChar: 'u32:1',
|
|
574
|
+
fNull: 'u32:1',
|
|
575
|
+
fRtsControl: 'u32:2',
|
|
576
|
+
fAbortOnError: 'u32:1',
|
|
577
|
+
fDummy2: 'u32:17',
|
|
578
|
+
wReserved: 'u16',
|
|
579
|
+
XonLim: 'u16',
|
|
580
|
+
XoffLim: 'u16',
|
|
581
|
+
ByteSize: 'u8',
|
|
582
|
+
Parity: 'u8',
|
|
583
|
+
StopBits: 'u8',
|
|
584
|
+
XonChar: 'i8',
|
|
585
|
+
XoffChar: 'i8',
|
|
586
|
+
ErrorChar: 'i8',
|
|
587
|
+
EofChar: 'i8',
|
|
588
|
+
EvtChar: 'i8',
|
|
589
|
+
wReserved1: 'u16',
|
|
590
|
+
});
|
|
591
|
+
t.check('DCB.size == 28', 28, DCB.size);
|
|
592
|
+
const d = DCB.decode(DCB.encode({
|
|
593
|
+
DCBlength: 28, BaudRate: 0x1C200,
|
|
594
|
+
fBinary: 1, fParity: 0, fOutxCtsFlow: 0, fOutxDsrFlow: 0,
|
|
595
|
+
fDtrControl: 2, fDsrSensitivity: 1, fTXContinueOnXoff: 0,
|
|
596
|
+
fOutX: 1, fInX: 0, fErrorChar: 1, fNull: 0,
|
|
597
|
+
fRtsControl: 2, fAbortOnError: 1, fDummy2: 0,
|
|
598
|
+
wReserved: 0, XonLim: 200, XoffLim: 100,
|
|
599
|
+
ByteSize: 8, Parity: 0, StopBits: 0,
|
|
600
|
+
XonChar: 3, XoffChar: 4, ErrorChar: 0xFF,
|
|
601
|
+
EofChar: 3, EvtChar: 0x4, wReserved1: 0,
|
|
602
|
+
}));
|
|
603
|
+
t.check('DCBlength', 28, d.DCBlength);
|
|
604
|
+
t.check('BaudRate', 0x1C200, d.BaudRate);
|
|
605
|
+
t.check('fBinary', 1, d.fBinary);
|
|
606
|
+
t.check('fParity', 0, d.fParity);
|
|
607
|
+
t.check('fDtrControl', 2, d.fDtrControl);
|
|
608
|
+
t.check('fDsrSensitivity', 1, d.fDsrSensitivity);
|
|
609
|
+
t.check('fOutX', 1, d.fOutX);
|
|
610
|
+
t.check('fRtsControl', 2, d.fRtsControl);
|
|
611
|
+
t.check('fAbortOnError', 1, d.fAbortOnError);
|
|
612
|
+
t.check('fDummy2', 0, d.fDummy2);
|
|
613
|
+
t.check('wReserved', 0, d.wReserved);
|
|
614
|
+
t.check('XonLim', 200, d.XonLim);
|
|
615
|
+
t.check('XoffLim', 100, d.XoffLim);
|
|
616
|
+
t.check('ByteSize', 8, d.ByteSize);
|
|
617
|
+
t.check('Parity', 0, d.Parity);
|
|
618
|
+
t.check('StopBits', 0, d.StopBits);
|
|
619
|
+
t.check('XonChar', 3, d.XonChar);
|
|
620
|
+
t.check('XoffChar', 4, d.XoffChar);
|
|
621
|
+
t.check('ErrorChar (i8)', -1, d.ErrorChar);
|
|
622
|
+
t.check('EofChar', 3, d.EofChar);
|
|
623
|
+
t.check('EvtChar', 4, d.EvtChar);
|
|
624
|
+
t.check('wReserved1', 0, d.wReserved1);
|
|
625
|
+
}
|
|
626
|
+
// 类型层:位域成员值类型是 number
|
|
627
|
+
{
|
|
628
|
+
const BF = struct('BF', {
|
|
629
|
+
a: 'u32:3',
|
|
630
|
+
s: 'i32:8',
|
|
631
|
+
});
|
|
632
|
+
expectType();
|
|
633
|
+
expectType();
|
|
634
|
+
}
|
|
635
|
+
// 位域 width>53:值域装不进安全整数 → bigint(与类型层 BitShape 同界)
|
|
636
|
+
t.section('bitfield width>53: bigint roundtrip (u64/i64)');
|
|
637
|
+
{
|
|
638
|
+
// u64:60 + u64:4 同单元混合形态:>53 的字段 bigint、≤53 的字段 number
|
|
639
|
+
const W64 = struct('W64', {
|
|
640
|
+
u: 'u64:60',
|
|
641
|
+
v: 'u64:4',
|
|
642
|
+
});
|
|
643
|
+
t.check('u64:60+u64:4 size == 8(同单元)', 8, W64.size);
|
|
644
|
+
t.check('v 与 u 同单元 offset == 0', 0, W64.offsetOf('v'));
|
|
645
|
+
const big = 0x0fffffffffffffffn; // 60 位内大值 > 2^53
|
|
646
|
+
const d1 = W64.decode(W64.encode({ u: big, v: 15 }));
|
|
647
|
+
t.check('u64:60 大值精确往返', big, d1.u);
|
|
648
|
+
t.check('u64:4 同单元往返', 15, d1.v);
|
|
649
|
+
expectType();
|
|
650
|
+
expectType();
|
|
651
|
+
// i64:60 负值符号扩展 → 精确 bigint(回归:曾被有符号分支的 Number() 截断)
|
|
652
|
+
const I64 = struct('I64', { s: 'i64:60' });
|
|
653
|
+
const neg = -12345678901234567n;
|
|
654
|
+
t.check('i64:60 负值精确往返', neg, I64.decode(I64.encode({ s: neg })).s);
|
|
655
|
+
// 符号位为 0 的正值:width>53 → 依然 bigint
|
|
656
|
+
t.check('i64:60 正值 bigint', 100n, I64.decode(I64.encode({ s: 100n })).s);
|
|
657
|
+
expectType();
|
|
658
|
+
if (false) {
|
|
659
|
+
// @ts-expect-error width>53 位域收 bigint,number 被类型层拒绝
|
|
660
|
+
I64.encode({ s: 42 });
|
|
661
|
+
}
|
|
662
|
+
// 分界:53 → number(2^53-1 精确),54 → bigint(2^54-1 精确)
|
|
663
|
+
const B53 = struct('B53', { a: 'u64:53' });
|
|
664
|
+
expectType();
|
|
665
|
+
t.check('u64:53 边界 number 精确', 9007199254740991, B53.decode(B53.encode({ a: 9007199254740991 })).a);
|
|
666
|
+
const B54 = struct('B54', { a: 'u64:54' });
|
|
667
|
+
expectType();
|
|
668
|
+
t.check('u64:54 边界 bigint 精确', 18014398509481983n, B54.decode(B54.encode({ a: 18014398509481983n })).a);
|
|
669
|
+
}
|
|
670
|
+
// ============ token @Enum:struct 字段值域 ============
|
|
671
|
+
t.section('struct 字段 @Enum: 词汇定义点拒绝 + 值域收窄 + 必填判定');
|
|
672
|
+
{
|
|
673
|
+
// 定义点拒绝(∉ SimpleValue —— 未知枚举名与拼错 token 同一道闸)。if(false)
|
|
674
|
+
// 纯类型层断言:位域反例运行时 normToken 也会 throw,不执行。
|
|
675
|
+
if (false) {
|
|
676
|
+
// @ts-expect-error 未知枚举名(TypoEnum ∉ keyof EnumMap)
|
|
677
|
+
struct('Bad1', { m: 'u32@TypoEnum' });
|
|
678
|
+
// @ts-expect-error 位域单元不收 @(值域标注对位域无意义,词汇拒)
|
|
679
|
+
struct('Bad2', { m: 'u32@WmMsg:3' });
|
|
680
|
+
// @ts-expect-error ptr 位未知枚举名
|
|
681
|
+
struct('Bad3', { p: '<>ptr@TypoEnum' });
|
|
682
|
+
// @ts-expect-error 数组糖元素同样受词汇守卫
|
|
683
|
+
struct('Bad4', { a: 'u32@TypoEnum[4]' });
|
|
684
|
+
}
|
|
685
|
+
// 合法定义:数字位 + ptr 位 @ 标注
|
|
686
|
+
const EV = struct('EV', {
|
|
687
|
+
msg: 'u32@WmMsg', // WM_NULL = 0 ∈ 值域 → encode 可省
|
|
688
|
+
key: 'u32@MouseKeyFlag', // 仅 MK_SHIFT = 4,0 ∉ 值域 → encode 必填
|
|
689
|
+
hp: '<>ptr@SetWindowPosHwnd', // ptr 位(HWND_TOP = 0 ∈ → 可省)
|
|
690
|
+
});
|
|
691
|
+
// decode:值域收窄(与 bind 签名位同构;异种枚举/域外字面量由枚举 nominal 拦)
|
|
692
|
+
const dev = EV.decode(EV.encode({ key: 4 }));
|
|
693
|
+
expectType();
|
|
694
|
+
expectType();
|
|
695
|
+
expectType();
|
|
696
|
+
t.check('数字位 @ 字段归一往返', 4, dev.key);
|
|
697
|
+
// encode 必填判定:0 ∉ 值域的 @ 字段必填(省略 = 新槽写 0 = 静默违约);
|
|
698
|
+
// 0 ∈ 值域可省。下面 { key } 行能编译即 msg/hp 可省的断言。
|
|
699
|
+
EV.encode({ key: 4 /* gui.MouseKeyFlag.MK_SHIFT */ });
|
|
700
|
+
// @ts-expect-error key 必填:省略即编译错(0 ∉ MouseKeyFlag)
|
|
701
|
+
EV.encode({ msg: 0 });
|
|
702
|
+
if (false) {
|
|
703
|
+
// @ts-expect-error 域外字面量(3 不是任何 MK_ 标志)
|
|
704
|
+
EV.encode({ key: 3 });
|
|
705
|
+
// @ts-expect-error 跨枚举误用(WmMsg 成员不是 MouseKeyFlag)
|
|
706
|
+
EV.encode({ key: 1 /* gui.WmMsg.CREATE */ });
|
|
707
|
+
}
|
|
708
|
+
// 数组糖元素带 @:lower 递归归一(normToken 剥 @)+ 元素值域
|
|
709
|
+
const EA = struct('EA', { msgs: 'u32@WmMsg[4]' });
|
|
710
|
+
const dea = EA.decode(EA.encode({ msgs: [0, 1, 2, 0] }));
|
|
711
|
+
expectType();
|
|
712
|
+
t.check('数组 @ 元素运行时归一往返', 2, dea.msgs[2]);
|
|
713
|
+
}
|
|
714
|
+
// ============ EncodeIn 后序穿透:嵌套/数组子树含必填 ⇒ 父键必给 ============
|
|
715
|
+
t.section('EncodeIn DFS 后序:子树含必填 ⇒ 键必填;值后序合成(非必填兄弟可省)');
|
|
716
|
+
{
|
|
717
|
+
// 嵌套子结构含 '!'(内层强制)+ 标量/字符串糖(内层可省:''/0 是合法零值)
|
|
718
|
+
const NSUB = struct('NSUB', { hp: '<X>ptr!', rest: 'u32', s: 'u16[4]@utf-8' });
|
|
719
|
+
const NOUT = struct('NOUT', { top: 'u32', sub: NSUB.__struct });
|
|
720
|
+
// decode 造全量对象(hp 槽预填非零,绕 '!' 的 decode 0 断言)
|
|
721
|
+
const nbuf = new PtrArrayBuffer(NOUT.size);
|
|
722
|
+
new DataView(nbuf).setUint32(NOUT.offsetOf('sub') + NSUB.offsetOf('hp'), 0x1234, true);
|
|
723
|
+
const dn = NOUT.decode(nbuf);
|
|
724
|
+
NOUT.encode(dn); // 全量回喂 ✓
|
|
725
|
+
NOUT.encode({ sub: dn.sub }); // 顶层 top 可省 ✓
|
|
726
|
+
// B 形态关键钉:内层非必填兄弟 rest/s 可省(A「全量严格形」形态此行编译错)
|
|
727
|
+
NOUT.encode({ sub: { hp: dn.sub.hp } });
|
|
728
|
+
// @ts-expect-error 嵌套穿透:sub 子树含 '!' ⇒ sub 必填 —— 省略即编译错
|
|
729
|
+
NOUT.encode({ top: 1 });
|
|
730
|
+
// @ts-expect-error 内层 '!' 必填:sub.hp 省略即编译错,不静默写 0
|
|
731
|
+
NOUT.encode({ sub: { rest: 1 } });
|
|
732
|
+
// 数组穿透:元素 0 ∉ 值域 ⇒ 整组必填(省 = 元素全 0 静默违约)
|
|
733
|
+
const ARMAND = struct('ARMAND', { keys: 'u32@MouseKeyFlag[2]' });
|
|
734
|
+
ARMAND.encode({ keys: [4, 4] });
|
|
735
|
+
// @ts-expect-error 数组含必填元素:整组省略即编译错
|
|
736
|
+
ARMAND.encode({});
|
|
737
|
+
// weak-type 防线在「无必填」分支保持:裸 number 与全可选形无公共键(TS2559)。
|
|
738
|
+
// if(false) 纯类型层断言(运行时 encode(123) 会炸,不执行)。
|
|
739
|
+
if (false) {
|
|
740
|
+
// @ts-expect-error 无必填 struct 的入参退化纯 DeepPartial,裸 number 被拦
|
|
741
|
+
RECT.encode(123);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
200
744
|
},
|
|
201
745
|
};
|