@lakutata/ffi 0.0.0-stage → 3.1.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.
Files changed (129) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +163 -2
  3. package/README.zh-CN.md +140 -0
  4. package/dist/cjs/components/NativeLibrary.d.ts +229 -0
  5. package/dist/cjs/components/NativeLibrary.js +346 -0
  6. package/dist/cjs/decorators/Library.d.ts +61 -0
  7. package/dist/cjs/decorators/Library.js +44 -0
  8. package/dist/cjs/decorators/Native.d.ts +69 -0
  9. package/dist/cjs/decorators/Native.js +90 -0
  10. package/dist/cjs/exceptions/NativeDeclarationException.d.ts +12 -0
  11. package/dist/cjs/exceptions/NativeDeclarationException.js +19 -0
  12. package/dist/cjs/exceptions/NativeLibraryClosedException.d.ts +12 -0
  13. package/dist/cjs/exceptions/NativeLibraryClosedException.js +19 -0
  14. package/dist/cjs/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
  15. package/dist/cjs/exceptions/NativeLibraryNotFoundException.js +18 -0
  16. package/dist/cjs/exceptions/NativeMemoryException.d.ts +11 -0
  17. package/dist/cjs/exceptions/NativeMemoryException.js +18 -0
  18. package/dist/cjs/exports/FFI.d.ts +12 -0
  19. package/dist/cjs/exports/FFI.js +40 -0
  20. package/dist/cjs/exports/Generator.d.ts +2 -0
  21. package/dist/cjs/exports/Generator.js +10 -0
  22. package/dist/cjs/lib/Memory.d.ts +188 -0
  23. package/dist/cjs/lib/Memory.js +227 -0
  24. package/dist/cjs/lib/Runtime.d.ts +98 -0
  25. package/dist/cjs/lib/Runtime.js +209 -0
  26. package/dist/cjs/lib/backend/Functions.d.ts +65 -0
  27. package/dist/cjs/lib/backend/Functions.js +214 -0
  28. package/dist/cjs/lib/backend/KoffiTypes.d.ts +53 -0
  29. package/dist/cjs/lib/backend/KoffiTypes.js +426 -0
  30. package/dist/cjs/lib/declarations/Builtins.d.ts +33 -0
  31. package/dist/cjs/lib/declarations/Builtins.js +179 -0
  32. package/dist/cjs/lib/declarations/Layout.d.ts +41 -0
  33. package/dist/cjs/lib/declarations/Layout.js +180 -0
  34. package/dist/cjs/lib/declarations/Lexer.d.ts +40 -0
  35. package/dist/cjs/lib/declarations/Lexer.js +325 -0
  36. package/dist/cjs/lib/declarations/Parser.d.ts +90 -0
  37. package/dist/cjs/lib/declarations/Parser.js +1425 -0
  38. package/dist/cjs/lib/declarations/Platform.d.ts +50 -0
  39. package/dist/cjs/lib/declarations/Platform.js +27 -0
  40. package/dist/cjs/lib/declarations/Types.d.ts +173 -0
  41. package/dist/cjs/lib/declarations/Types.js +179 -0
  42. package/dist/cjs/lib/generator/CText.d.ts +14 -0
  43. package/dist/cjs/lib/generator/CText.js +133 -0
  44. package/dist/cjs/lib/generator/Generator.d.ts +136 -0
  45. package/dist/cjs/lib/generator/Generator.js +395 -0
  46. package/dist/cjs/lib/generator/Preprocessor.d.ts +60 -0
  47. package/dist/cjs/lib/generator/Preprocessor.js +94 -0
  48. package/dist/cjs/lib/generator/TypeScriptTypes.d.ts +35 -0
  49. package/dist/cjs/lib/generator/TypeScriptTypes.js +153 -0
  50. package/dist/cjs/lib/library/NativeCallback.d.ts +44 -0
  51. package/dist/cjs/lib/library/NativeCallback.js +66 -0
  52. package/dist/cjs/lib/library/NativeLibraryHandle.d.ts +268 -0
  53. package/dist/cjs/lib/library/NativeLibraryHandle.js +540 -0
  54. package/dist/cjs/lib/library/Resolve.d.ts +49 -0
  55. package/dist/cjs/lib/library/Resolve.js +130 -0
  56. package/dist/cjs/lib/thread/NativeThreads.d.ts +80 -0
  57. package/dist/cjs/lib/thread/NativeThreads.js +384 -0
  58. package/dist/cjs/lib/thread/NativeWorker.d.ts +6 -0
  59. package/dist/cjs/lib/thread/NativeWorker.js +96 -0
  60. package/dist/cjs/lib/thread/NativeWorkerCode.d.ts +5 -0
  61. package/dist/cjs/lib/thread/NativeWorkerCode.js +4 -0
  62. package/dist/cjs/lib/thread/PackageRequire.d.ts +6 -0
  63. package/dist/cjs/lib/thread/PackageRequire.js +4 -0
  64. package/dist/cjs/lib/thread/ThreadProtocol.d.ts +77 -0
  65. package/dist/cjs/lib/thread/ThreadProtocol.js +2 -0
  66. package/dist/cjs/package.json +1 -0
  67. package/dist/esm/components/NativeLibrary.js +338 -0
  68. package/dist/esm/decorators/Library.js +40 -0
  69. package/dist/esm/decorators/Native.js +85 -0
  70. package/dist/esm/exceptions/NativeDeclarationException.js +15 -0
  71. package/dist/esm/exceptions/NativeLibraryClosedException.js +15 -0
  72. package/dist/esm/exceptions/NativeLibraryNotFoundException.js +14 -0
  73. package/dist/esm/exceptions/NativeMemoryException.js +14 -0
  74. package/dist/esm/exports/FFI.js +10 -0
  75. package/dist/esm/exports/Generator.js +2 -0
  76. package/dist/esm/lib/Memory.js +210 -0
  77. package/dist/esm/lib/Runtime.js +192 -0
  78. package/dist/esm/lib/backend/Functions.js +206 -0
  79. package/dist/esm/lib/backend/KoffiTypes.js +418 -0
  80. package/dist/esm/lib/declarations/Builtins.js +174 -0
  81. package/dist/esm/lib/declarations/Layout.js +175 -0
  82. package/dist/esm/lib/declarations/Lexer.js +321 -0
  83. package/dist/esm/lib/declarations/Parser.js +1421 -0
  84. package/dist/esm/lib/declarations/Platform.js +23 -0
  85. package/dist/esm/lib/declarations/Types.js +170 -0
  86. package/dist/esm/lib/generator/CText.js +129 -0
  87. package/dist/esm/lib/generator/Generator.js +388 -0
  88. package/dist/esm/lib/generator/Preprocessor.js +87 -0
  89. package/dist/esm/lib/generator/TypeScriptTypes.js +149 -0
  90. package/dist/esm/lib/library/NativeCallback.js +59 -0
  91. package/dist/esm/lib/library/NativeLibraryHandle.js +532 -0
  92. package/dist/esm/lib/library/Resolve.js +122 -0
  93. package/dist/esm/lib/thread/NativeThreads.js +377 -0
  94. package/dist/esm/lib/thread/NativeWorker.js +93 -0
  95. package/dist/esm/lib/thread/NativeWorkerCode.js +1 -0
  96. package/dist/esm/lib/thread/PackageRequire.js +2 -0
  97. package/dist/esm/lib/thread/ThreadProtocol.js +1 -0
  98. package/dist/types/components/NativeLibrary.d.ts +229 -0
  99. package/dist/types/decorators/Library.d.ts +61 -0
  100. package/dist/types/decorators/Native.d.ts +69 -0
  101. package/dist/types/exceptions/NativeDeclarationException.d.ts +12 -0
  102. package/dist/types/exceptions/NativeLibraryClosedException.d.ts +12 -0
  103. package/dist/types/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
  104. package/dist/types/exceptions/NativeMemoryException.d.ts +11 -0
  105. package/dist/types/exports/FFI.d.ts +12 -0
  106. package/dist/types/exports/Generator.d.ts +2 -0
  107. package/dist/types/lib/Memory.d.ts +188 -0
  108. package/dist/types/lib/Runtime.d.ts +98 -0
  109. package/dist/types/lib/backend/Functions.d.ts +65 -0
  110. package/dist/types/lib/backend/KoffiTypes.d.ts +53 -0
  111. package/dist/types/lib/declarations/Builtins.d.ts +33 -0
  112. package/dist/types/lib/declarations/Layout.d.ts +41 -0
  113. package/dist/types/lib/declarations/Lexer.d.ts +40 -0
  114. package/dist/types/lib/declarations/Parser.d.ts +90 -0
  115. package/dist/types/lib/declarations/Platform.d.ts +50 -0
  116. package/dist/types/lib/declarations/Types.d.ts +173 -0
  117. package/dist/types/lib/generator/CText.d.ts +14 -0
  118. package/dist/types/lib/generator/Generator.d.ts +136 -0
  119. package/dist/types/lib/generator/Preprocessor.d.ts +60 -0
  120. package/dist/types/lib/generator/TypeScriptTypes.d.ts +35 -0
  121. package/dist/types/lib/library/NativeCallback.d.ts +44 -0
  122. package/dist/types/lib/library/NativeLibraryHandle.d.ts +268 -0
  123. package/dist/types/lib/library/Resolve.d.ts +49 -0
  124. package/dist/types/lib/thread/NativeThreads.d.ts +80 -0
  125. package/dist/types/lib/thread/NativeWorker.d.ts +6 -0
  126. package/dist/types/lib/thread/NativeWorkerCode.d.ts +5 -0
  127. package/dist/types/lib/thread/PackageRequire.d.ts +6 -0
  128. package/dist/types/lib/thread/ThreadProtocol.d.ts +77 -0
  129. package/package.json +85 -4
@@ -0,0 +1,1421 @@
1
+ import { annotationDirection, builtinTypes, CONSTS, CONVENTIONS, IGNORED, INLINES } from './Builtins.js';
2
+ import { alignmentOf, sizeOf } from './Layout.js';
3
+ import { declarationError, tokenize } from './Lexer.js';
4
+ import { emptyDeclarations, primitiveSigned, primitiveSize, typeText, VOID } from './Types.js';
5
+ /**
6
+ * The words of the arithmetic types, combined as C combines them (unsigned long long int)
7
+ */
8
+ const TYPE_WORDS = new Set(['void', 'char', 'short', 'int', 'long', 'float', 'double', 'signed', 'unsigned', '__signed', '__signed__', '_Bool', '__int8', '__int16', '__int32', '__int64', '__int128', '_Complex', '__complex__', '_Float16', '__fp16', '_Float128', '__float128']);
9
+ const ATTRIBUTES = new Set(['__attribute__', '__attribute', '__declspec']);
10
+ const ASM = new Set(['__asm__', '__asm', 'asm']);
11
+ const ALIGNAS = new Set(['_Alignas', 'alignas', '__alignas']);
12
+ /**
13
+ * Reads C declarations: typedefs, structs, unions, enums, function prototypes and variables, for one platform. Each
14
+ * library has its own declarations: their names do not meet the ones of another library
15
+ */
16
+ export class DeclarationParser {
17
+ #platform;
18
+ #declarations;
19
+ #builtins;
20
+ #tokens = [];
21
+ #index = 0;
22
+ //The packing of #pragma pack, and its stack
23
+ #pack;
24
+ #packs = [];
25
+ //When a header is read by readSource: its declarations, the names the one being read declares, where the macros are
26
+ #items;
27
+ #recording;
28
+ #macroOrigins;
29
+ /**
30
+ * Read declarations for a platform.
31
+ * @param platform The platform: the sizes of the types and the layout of the structs depend on it.
32
+ * @param declarations Declarations already read, added to (the ones of the library, when a prototype is read).
33
+ */
34
+ constructor(platform, declarations = emptyDeclarations()) {
35
+ this.#platform = platform;
36
+ this.#declarations = declarations;
37
+ this.#builtins = builtinTypes(platform);
38
+ }
39
+ /**
40
+ * The declarations read so far.
41
+ */
42
+ get declarations() {
43
+ return this.#declarations;
44
+ }
45
+ /**
46
+ * Read a text of declarations, adding what it declares.
47
+ * @param text The declarations, as a header writes them (preprocessed or not: #pragma pack is read, #define and
48
+ * #include are skipped, #if and its kind are refused).
49
+ */
50
+ parse(text) {
51
+ this.#tokens = this.#expand(tokenize(text));
52
+ this.#index = 0;
53
+ while (!this.#at('end'))
54
+ this.#declaration();
55
+ this.#macroConstants();
56
+ return this.#declarations;
57
+ }
58
+ /**
59
+ * Read a preprocessed header for the generator of libraries: each declaration kept with the file it comes from and
60
+ * what it declares; one that cannot be read is skipped, its error kept, and the next ones are read.
61
+ * @param text The output of the preprocessor (`cc -E -dD`, `cl /P`): its line markers give the files.
62
+ * @returns The declarations, each declaration as it is written, and where the macros are defined.
63
+ */
64
+ readSource(text) {
65
+ const items = [];
66
+ const macros = new Map();
67
+ this.#macroOrigins = macros;
68
+ try {
69
+ this.#tokens = this.#expand(tokenize(text));
70
+ }
71
+ finally {
72
+ this.#macroOrigins = undefined;
73
+ }
74
+ this.#index = 0;
75
+ this.#items = items;
76
+ try {
77
+ while (!this.#at('end'))
78
+ this.#item();
79
+ }
80
+ finally {
81
+ this.#items = undefined;
82
+ }
83
+ this.#macroConstants();
84
+ return { declarations: this.#declarations, items: items, macros: macros };
85
+ }
86
+ /**
87
+ * Read the prototype of one function (a trailing semicolon is optional).
88
+ * @param text The prototype: `int sqlite3_open(const char *filename, _Out_ sqlite3 **db)`.
89
+ */
90
+ prototype(text) {
91
+ this.#tokens = this.#expand(tokenize(text));
92
+ this.#index = 0;
93
+ const start = this.#peek();
94
+ const specifiers = this.#specifiers(false);
95
+ const declarator = this.#declarator(false);
96
+ const { type } = declarator.build(specifiers.base, specifiers.constant);
97
+ if (type.kind !== 'function' || !declarator.name)
98
+ throw this.#error('A function prototype is expected', start);
99
+ this.#attributes();
100
+ const asm = this.#asmLabel() ?? declarator.asm;
101
+ if (this.#isPunctuator(';'))
102
+ this.#next();
103
+ if (!this.#at('end'))
104
+ throw this.#error(`Unexpected ${this.#describe(this.#peek())} after the prototype`, this.#peek());
105
+ return { name: declarator.name, symbol: this.#symbol(declarator.name, asm), type: this.#withConvention(type, declarator.convention ?? specifiers.convention) };
106
+ }
107
+ /**
108
+ * Read a type name: `struct timeval`, `const char *`, `int[4]`.
109
+ * @param text The type.
110
+ */
111
+ typeName(text) {
112
+ this.#tokens = this.#expand(tokenize(text));
113
+ this.#index = 0;
114
+ const specifiers = this.#specifiers(false);
115
+ const declarator = this.#declarator(true);
116
+ if (declarator.name)
117
+ throw this.#error(`A type is expected, not a declaration of ${declarator.name}`, declarator.token);
118
+ if (!this.#at('end'))
119
+ throw this.#error(`Unexpected ${this.#describe(this.#peek())} after the type`, this.#peek());
120
+ return declarator.build(specifiers.base, specifiers.constant).type;
121
+ }
122
+ /**
123
+ * The value of a constant expression of the declarations (`SQLITE_OK + 1`, `1 << 4`).
124
+ * @param text The expression.
125
+ */
126
+ constant(text) {
127
+ this.#tokens = this.#expand(tokenize(text));
128
+ this.#index = 0;
129
+ const value = this.#expression();
130
+ if (!this.#at('end'))
131
+ throw this.#error(`Unexpected ${this.#describe(this.#peek())} after the expression`, this.#peek());
132
+ return value.value;
133
+ }
134
+ /**
135
+ * The tokens with the macros without arguments expanded where they are used (their definitions removed from them);
136
+ * a macro with arguments used is refused
137
+ * @param tokens
138
+ */
139
+ #expand(tokens) {
140
+ const macros = this.#declarations.macros;
141
+ const expanded = [];
142
+ const expandInto = (token, active, at) => {
143
+ const body = token.kind === 'identifier' && !active.has(token.text) ? macros.get(token.text) : undefined;
144
+ //A header read by readSource is preprocessed: a name left there is not a use of the macro
145
+ if (body === null && !this.#macroOrigins)
146
+ throw this.#error(`The macro ${token.text} takes arguments: the macros with arguments are not supported (write what it expands to)`, at);
147
+ if (!body)
148
+ return void expanded.push(token);
149
+ const inner = new Set(active).add(token.text);
150
+ for (const part of body)
151
+ expandInto({ ...part, line: at.line, column: at.column, ...at.file !== undefined ? { file: at.file, fileLine: at.fileLine } : {} }, inner, at);
152
+ };
153
+ for (const token of tokens) {
154
+ if (token.kind === 'define') {
155
+ this.#define(token);
156
+ continue;
157
+ }
158
+ if (token.kind === 'undef') {
159
+ macros.delete(token.text.match(/^[A-Za-z_]\w*/)?.[0] ?? '');
160
+ continue;
161
+ }
162
+ expandInto(token, new Set(), token);
163
+ }
164
+ return this.#pragmaOperators(expanded);
165
+ }
166
+ /**
167
+ * The __pragma(…) of Microsoft: __pragma(pack(…)) read as a #pragma pack, the others removed
168
+ * @param tokens
169
+ */
170
+ #pragmaOperators(tokens) {
171
+ if (!tokens.some((token) => token.kind === 'identifier' && token.text === '__pragma'))
172
+ return tokens;
173
+ const result = [];
174
+ for (let index = 0; index < tokens.length; index++) {
175
+ const token = tokens[index];
176
+ if (token.kind !== 'identifier' || token.text !== '__pragma' || tokens[index + 1]?.text !== '(') {
177
+ result.push(token);
178
+ continue;
179
+ }
180
+ let depth = 0;
181
+ let end = index + 1;
182
+ for (; end < tokens.length - 1; end++) {
183
+ if (tokens[end].kind !== 'punctuator')
184
+ continue;
185
+ if (tokens[end].text === '(')
186
+ depth++;
187
+ else if (tokens[end].text === ')' && --depth === 0)
188
+ break;
189
+ }
190
+ const inner = tokens.slice(index + 2, end);
191
+ if (inner[0]?.text === 'pack' && inner[1]?.text === '(')
192
+ result.push({ ...token, kind: 'pragma', text: `pack(${inner.slice(2, -1).map((part) => part.text).join('')})` });
193
+ index = end;
194
+ }
195
+ return result;
196
+ }
197
+ /**
198
+ * A #define: a macro without arguments (its tokens kept), or one with arguments (refused where used)
199
+ * @param token
200
+ */
201
+ #define(token) {
202
+ const name = token.text.match(/^[A-Za-z_]\w*/)?.[0];
203
+ if (!name)
204
+ throw this.#error('#define without a name', token);
205
+ const rest = token.text.slice(name.length);
206
+ this.#macroOrigins?.set(name, { ...token.file !== undefined ? { file: token.file } : {}, ...token.fileLine !== undefined ? { line: token.fileLine } : {} });
207
+ if (rest.startsWith('(')) {
208
+ this.#declarations.macros.set(name, null);
209
+ return;
210
+ }
211
+ const body = [];
212
+ let parts;
213
+ try {
214
+ parts = tokenize(rest);
215
+ }
216
+ catch (error) {
217
+ //A header read by readSource: a macro that cannot be read is not used
218
+ if (!this.#macroOrigins)
219
+ throw error;
220
+ this.#declarations.macros.set(name, null);
221
+ return;
222
+ }
223
+ for (const part of parts) {
224
+ if (part.kind === 'end')
225
+ break;
226
+ if (part.kind === 'pragma' || part.kind === 'define' || part.kind === 'undef')
227
+ continue;
228
+ body.push({ kind: part.kind, text: part.text, ...part.value !== undefined ? { value: part.value } : {}, ...part.float ? { float: true } : {} });
229
+ }
230
+ this.#declarations.macros.set(name, body);
231
+ }
232
+ /**
233
+ * The macros whose value is an integer constant expression, as constants
234
+ */
235
+ #macroConstants() {
236
+ const tokens = this.#tokens;
237
+ const index = this.#index;
238
+ for (const [name, body] of this.#declarations.macros) {
239
+ if (!body?.length || this.#declarations.constants.has(name))
240
+ continue;
241
+ try {
242
+ const end = { kind: 'end', text: '', line: 0, column: 0 };
243
+ this.#tokens = [...this.#expand([{ kind: 'identifier', text: name, line: 0, column: 0 }]), end];
244
+ this.#index = 0;
245
+ const value = this.#conditional();
246
+ if (this.#at('end'))
247
+ this.#declarations.constants.set(name, value.value);
248
+ }
249
+ catch {
250
+ //Not an integer constant (a string, a type, an expression of what is not constant)
251
+ }
252
+ }
253
+ this.#tokens = tokens;
254
+ this.#index = index;
255
+ }
256
+ /**
257
+ * A declaration of a header read by readSource, kept with what it declares; one that cannot be read is skipped
258
+ */
259
+ #item() {
260
+ const start = this.#index;
261
+ const first = this.#peek();
262
+ //extern "C" { … }: its declarations are the items
263
+ if (first.kind === 'identifier' && first.text === 'extern' && this.#peek(1).kind === 'string' && this.#isPunctuator('{', 2)) {
264
+ this.#index += 3;
265
+ while (!this.#isPunctuator('}') && !this.#at('end'))
266
+ this.#item();
267
+ if (this.#isPunctuator('}'))
268
+ this.#next();
269
+ return;
270
+ }
271
+ const pack = this.#pack;
272
+ const names = [];
273
+ this.#recording = names;
274
+ let error;
275
+ try {
276
+ this.#declaration();
277
+ }
278
+ catch (thrown) {
279
+ error = (thrown instanceof Error ? thrown.message : String(thrown)).replace(/ \(line \d+, column \d+\)$/, '');
280
+ this.#index = this.#recover(start);
281
+ }
282
+ finally {
283
+ this.#recording = undefined;
284
+ }
285
+ this.#items.push({
286
+ ...first.file !== undefined ? { file: first.file } : {},
287
+ ...first.fileLine !== undefined ? { line: first.fileLine } : {},
288
+ tokens: this.#tokens.slice(start, this.#index),
289
+ names: names,
290
+ ...pack !== undefined ? { pack: pack } : {},
291
+ ...error !== undefined ? { error: error } : {}
292
+ });
293
+ }
294
+ /**
295
+ * Where the declaration that could not be read ends: after its semicolon, or after the body of a function defined
296
+ * in the header
297
+ * @param start
298
+ */
299
+ #recover(start) {
300
+ const tokens = this.#tokens;
301
+ let depth = 0;
302
+ let body = false;
303
+ for (let index = start; index < tokens.length - 1; index++) {
304
+ const token = tokens[index];
305
+ if (token.kind === 'pragma' && depth === 0 && index > start)
306
+ return index;
307
+ if (token.kind !== 'punctuator')
308
+ continue;
309
+ if (token.text === '{' && depth === 0)
310
+ body = index > start && tokens[index - 1].text === ')';
311
+ if (token.text === '(' || token.text === '[' || token.text === '{') {
312
+ depth++;
313
+ }
314
+ else if (token.text === ')' || token.text === ']' || token.text === '}') {
315
+ depth = Math.max(0, depth - 1);
316
+ if (token.text === '}' && depth === 0 && body)
317
+ return tokens[index + 1]?.text === ';' ? index + 2 : index + 1;
318
+ }
319
+ else if (token.text === ';' && depth === 0) {
320
+ return index + 1;
321
+ }
322
+ }
323
+ return tokens.length - 1;
324
+ }
325
+ /**
326
+ * A name declared, for readSource
327
+ * @param name
328
+ */
329
+ #defined(name) {
330
+ this.#recording?.push(name);
331
+ }
332
+ /**
333
+ * A declaration, or a #pragma pack
334
+ */
335
+ #declaration() {
336
+ const token = this.#peek();
337
+ if (token.kind === 'pragma') {
338
+ this.#next();
339
+ return this.#pragmaPack(token);
340
+ }
341
+ if (this.#isPunctuator(';')) {
342
+ this.#next();
343
+ return;
344
+ }
345
+ //extern "C" { … } of the headers shared with C++
346
+ if (token.kind === 'identifier' && token.text === 'extern' && this.#peek(1).kind === 'string') {
347
+ this.#next();
348
+ this.#next();
349
+ if (this.#isPunctuator('{')) {
350
+ this.#next();
351
+ while (!this.#isPunctuator('}')) {
352
+ if (this.#at('end'))
353
+ throw this.#error('extern "C" { is not closed', token);
354
+ this.#declaration();
355
+ }
356
+ this.#next();
357
+ }
358
+ return;
359
+ }
360
+ //_Static_assert
361
+ if (token.kind === 'identifier' && ['_Static_assert', 'static_assert'].includes(token.text)) {
362
+ this.#next();
363
+ this.#skipBalanced();
364
+ this.#expectPunctuator(';');
365
+ return;
366
+ }
367
+ const specifiers = this.#specifiers(true);
368
+ if (this.#isPunctuator(';')) {
369
+ this.#next();
370
+ return;
371
+ }
372
+ for (;;) {
373
+ const declarator = this.#declarator(false);
374
+ this.#attributes();
375
+ const asm = this.#asmLabel() ?? declarator.asm;
376
+ this.#attributes();
377
+ //A function defined here (an inline function of a header): its body is skipped
378
+ if (this.#isPunctuator('{')) {
379
+ this.#skipBalanced();
380
+ return;
381
+ }
382
+ let initializer;
383
+ if (this.#isPunctuator('=')) {
384
+ this.#next();
385
+ initializer = this.#initializer();
386
+ }
387
+ this.#declare(specifiers, declarator, asm, initializer);
388
+ if (this.#isPunctuator(',')) {
389
+ this.#next();
390
+ continue;
391
+ }
392
+ this.#expectPunctuator(';');
393
+ return;
394
+ }
395
+ }
396
+ /**
397
+ * What a declarator of a declaration declares: a typedef, a function, a variable, a constant
398
+ * @param specifiers
399
+ * @param declarator
400
+ * @param asm
401
+ * @param initializer
402
+ */
403
+ #declare(specifiers, declarator, asm, initializer) {
404
+ if (!declarator.name)
405
+ throw this.#error('A name is expected in the declaration', this.#peek());
406
+ const name = declarator.name;
407
+ const { type, constant } = declarator.build(specifiers.base, specifiers.constant);
408
+ if (specifiers.storage === 'typedef') {
409
+ const previous = this.#declarations.types.get(name);
410
+ if (previous && previous !== type && typeText(previous) !== typeText(type))
411
+ throw this.#error(`The type ${name} is declared again as another type (${typeText(previous)}, then ${typeText(type)})`, declarator.token);
412
+ this.#declarations.types.set(name, type);
413
+ this.#defined(`type ${name}`);
414
+ return;
415
+ }
416
+ if (type.kind === 'function') {
417
+ if (specifiers.storage === 'static')
418
+ return;
419
+ this.#declarations.functions.set(name, { name: name, symbol: this.#symbol(name, asm), type: this.#withConvention(type, declarator.convention ?? specifiers.convention) });
420
+ this.#defined(`function ${name}`);
421
+ return;
422
+ }
423
+ //static const int LIMIT = 10;: a constant
424
+ if (specifiers.storage === 'static') {
425
+ if (initializer && constant) {
426
+ this.#declarations.constants.set(name, initializer.value);
427
+ this.#defined(`constant ${name}`);
428
+ }
429
+ return;
430
+ }
431
+ if (specifiers.threadLocal)
432
+ throw this.#error(`The variable ${name} is thread-local: it cannot be reached through the library`, declarator.token);
433
+ this.#declarations.variables.set(name, { name: name, symbol: this.#symbol(name, asm), type: type, constant: constant });
434
+ this.#defined(`variable ${name}`);
435
+ }
436
+ /**
437
+ * The symbol of a function or a variable: its name, or the label of its __asm (without the underscore the binaries
438
+ * of macOS add)
439
+ * @param name
440
+ * @param asm
441
+ */
442
+ #symbol(name, asm) {
443
+ if (asm === undefined)
444
+ return name;
445
+ return this.#platform.underscoredSymbols && asm.startsWith('_') ? asm.slice(1) : asm;
446
+ }
447
+ /**
448
+ * A function type with its calling convention
449
+ * @param type
450
+ * @param convention
451
+ */
452
+ #withConvention(type, convention) {
453
+ //A variadic function is called as cdecl whatever it is declared with, as Microsoft compiles it
454
+ if (type.variadic)
455
+ return type;
456
+ return convention && convention !== type.convention ? { ...type, convention: convention } : type;
457
+ }
458
+ /**
459
+ * #pragma pack(n), pack(push, n), pack(push, name, n), pack(pop), pack(pop, name), pack()
460
+ * @param token
461
+ */
462
+ #pragmaPack(token) {
463
+ const args = token.text.slice(5, -1).split(',').filter(Boolean);
464
+ const value = (text) => {
465
+ if (text === undefined)
466
+ return undefined;
467
+ const number = Number(text);
468
+ if (![1, 2, 4, 8, 16].includes(number))
469
+ throw this.#error(`#pragma pack(${text}) is not a packing of 1, 2, 4, 8 or 16`, token);
470
+ return number >= 16 ? undefined : number;
471
+ };
472
+ if (!args.length) {
473
+ this.#pack = undefined;
474
+ }
475
+ else if (args[0] === 'push') {
476
+ this.#packs.push(this.#pack);
477
+ const number = args.slice(1).find((arg) => /^\d+$/.test(arg));
478
+ if (number !== undefined)
479
+ this.#pack = value(number);
480
+ }
481
+ else if (args[0] === 'pop') {
482
+ this.#pack = this.#packs.length ? this.#packs.pop() : undefined;
483
+ }
484
+ else {
485
+ this.#pack = value(args[0]);
486
+ }
487
+ }
488
+ /**
489
+ * The specifiers of a declaration: the storage, the qualifiers, the base type (arithmetic words, a typedef, a
490
+ * struct, a union, an enum), the calling convention, the annotations
491
+ * @param declaration whether a whole declaration is read (typedef, extern and static are allowed)
492
+ */
493
+ #specifiers(declaration) {
494
+ const start = this.#peek();
495
+ const words = [];
496
+ let base;
497
+ const specifiers = { base: VOID, inline: false, constant: false, threadLocal: false };
498
+ for (;;) {
499
+ const token = this.#peek();
500
+ if (token.kind !== 'identifier') {
501
+ //[[nodiscard]]
502
+ if (this.#isPunctuator('[') && this.#isPunctuator('[', 1)) {
503
+ this.#skipBalanced();
504
+ continue;
505
+ }
506
+ break;
507
+ }
508
+ const word = token.text;
509
+ if (CONSTS.has(word)) {
510
+ this.#next();
511
+ specifiers.constant = true;
512
+ continue;
513
+ }
514
+ if (IGNORED.has(word)) {
515
+ this.#next();
516
+ continue;
517
+ }
518
+ if (INLINES.has(word)) {
519
+ this.#next();
520
+ specifiers.inline = true;
521
+ continue;
522
+ }
523
+ if (CONVENTIONS.has(word)) {
524
+ this.#next();
525
+ specifiers.convention = CONVENTIONS.get(word);
526
+ continue;
527
+ }
528
+ if (ATTRIBUTES.has(word)) {
529
+ const align = this.#attributes()?.align;
530
+ if (align)
531
+ specifiers.align = align;
532
+ continue;
533
+ }
534
+ if (ALIGNAS.has(word)) {
535
+ specifiers.align = this.#alignas();
536
+ continue;
537
+ }
538
+ if (word === 'typedef' || word === 'extern' || word === 'static') {
539
+ if (!declaration)
540
+ throw this.#error(`${word} is not allowed here`, token);
541
+ this.#next();
542
+ specifiers.storage = word;
543
+ continue;
544
+ }
545
+ if (word === '_Thread_local' || word === '__thread' || word === 'thread_local') {
546
+ this.#next();
547
+ specifiers.threadLocal = true;
548
+ continue;
549
+ }
550
+ if (word === 'struct' || word === 'union') {
551
+ if (base || words.length)
552
+ throw this.#error(`${word} after another type`, token);
553
+ base = this.#record(word);
554
+ continue;
555
+ }
556
+ if (word === 'enum') {
557
+ if (base || words.length)
558
+ throw this.#error(`enum after another type`, token);
559
+ base = this.#enumeration();
560
+ continue;
561
+ }
562
+ if (TYPE_WORDS.has(word)) {
563
+ if (base)
564
+ throw this.#error(`${word} after another type`, token);
565
+ this.#next();
566
+ words.push(word);
567
+ continue;
568
+ }
569
+ //An annotation (_Out_, _In_reads_(n), OUT), unless a type of that name is declared
570
+ if (!this.#typedef(word)) {
571
+ const direction = annotationDirection(word);
572
+ if (direction !== undefined) {
573
+ this.#next();
574
+ if (this.#isPunctuator('('))
575
+ this.#skipBalanced();
576
+ if (direction)
577
+ specifiers.direction = direction;
578
+ continue;
579
+ }
580
+ }
581
+ //A typedef name, when no type was given yet; otherwise it is the name declared
582
+ if (!base && !words.length) {
583
+ const typedef = this.#typedef(word);
584
+ if (typedef) {
585
+ this.#next();
586
+ base = typedef;
587
+ continue;
588
+ }
589
+ throw this.#unknownType(token);
590
+ }
591
+ break;
592
+ }
593
+ if (words.length)
594
+ base = this.#arithmetic(words, start);
595
+ if (!base)
596
+ throw this.#peek().kind === 'identifier' ? this.#unknownType(this.#peek()) : this.#error(`A type is expected, not ${this.#describe(this.#peek())}`, this.#peek());
597
+ specifiers.base = base;
598
+ return specifiers;
599
+ }
600
+ /**
601
+ * The exception of a name used as a type and not declared; a name in capitals may be a macro
602
+ * @param token
603
+ */
604
+ #unknownType(token) {
605
+ const macro = /^[A-Z][A-Z0-9_]*$/.test(token.text) ? ': a macro? give its #define with the declarations' : '';
606
+ return this.#error(`Unknown type ${token.text}${macro}`, token);
607
+ }
608
+ /**
609
+ * The arithmetic type of the words of a declaration (unsigned long long int)
610
+ * @param words
611
+ * @param token
612
+ */
613
+ #arithmetic(words, token) {
614
+ const count = (word) => words.filter((candidate) => candidate === word).length;
615
+ const unsigned = count('unsigned') > 0;
616
+ const signed = count('signed') + count('__signed') + count('__signed__') > 0;
617
+ const longs = count('long');
618
+ const name = (value) => ({ kind: 'primitive', name: value });
619
+ const has = (word) => words.includes(word);
620
+ if (['__int128', '_Complex', '__complex__', '_Float16', '__fp16', '_Float128', '__float128'].some(has))
621
+ throw this.#error(`The type ${words.join(' ')} is not supported`, token);
622
+ if (has('void'))
623
+ return VOID;
624
+ if (has('_Bool'))
625
+ return name('bool');
626
+ if (has('float'))
627
+ return name('float');
628
+ if (has('double')) {
629
+ if (longs)
630
+ throw this.#error('long double is not supported', token);
631
+ return name('double');
632
+ }
633
+ if (has('char'))
634
+ return name(unsigned ? 'uchar' : signed ? 'schar' : 'char');
635
+ if (has('__int8'))
636
+ return name(unsigned ? 'uint8' : 'int8');
637
+ if (has('__int16'))
638
+ return name(unsigned ? 'uint16' : 'int16');
639
+ if (has('__int32'))
640
+ return name(unsigned ? 'uint32' : 'int32');
641
+ if (has('__int64'))
642
+ return name(unsigned ? 'uint64' : 'int64');
643
+ if (has('short'))
644
+ return name(unsigned ? 'ushort' : 'short');
645
+ if (longs >= 2)
646
+ return name(unsigned ? 'ulonglong' : 'longlong');
647
+ if (longs === 1)
648
+ return name(unsigned ? 'ulong' : 'long');
649
+ return name(unsigned ? 'uint' : 'int');
650
+ }
651
+ /**
652
+ * The type of a typedef name: declared, or built in
653
+ * @param name
654
+ */
655
+ #typedef(name) {
656
+ return this.#declarations.types.get(name) ?? this.#builtins.get(name);
657
+ }
658
+ /**
659
+ * A struct or a union: a reference to its tag, its declaration, or its definition
660
+ * @param kind
661
+ */
662
+ #record(kind) {
663
+ const keyword = this.#next();
664
+ let attributes = this.#attributes();
665
+ let tag;
666
+ let tagToken;
667
+ if (this.#peek().kind === 'identifier' && !ATTRIBUTES.has(this.#peek().text)) {
668
+ tagToken = this.#next();
669
+ tag = tagToken.text;
670
+ attributes = this.#attributes() ?? attributes;
671
+ }
672
+ const key = `${kind} ${tag}`;
673
+ let record;
674
+ if (tag) {
675
+ const known = this.#declarations.tags.get(key);
676
+ if (known && known.kind !== kind)
677
+ throw this.#error(`${tag} is a ${known.kind}, not a ${kind}`, tagToken);
678
+ record = known;
679
+ }
680
+ if (!this.#isPunctuator('{')) {
681
+ if (!tag)
682
+ throw this.#error(`A ${kind} without a name must be defined`, keyword);
683
+ if (!record) {
684
+ //Declared, not defined: an opaque type, completed if defined later
685
+ record = { kind: kind, tag: tag };
686
+ this.#declarations.tags.set(key, record);
687
+ }
688
+ return record;
689
+ }
690
+ if (record?.members)
691
+ throw this.#error(`The ${kind} ${tag} is defined again`, tagToken);
692
+ if (!record) {
693
+ record = { kind: kind, tag: tag };
694
+ if (tag)
695
+ this.#declarations.tags.set(key, record);
696
+ }
697
+ this.#next();
698
+ const members = [];
699
+ let anonymous = 0;
700
+ while (!this.#isPunctuator('}')) {
701
+ if (this.#at('end'))
702
+ throw this.#error(`The ${kind} ${tag ?? ''} is not closed`.replace(' ', ' '), keyword);
703
+ if (this.#peek().kind === 'pragma') {
704
+ this.#pragmaPack(this.#next());
705
+ continue;
706
+ }
707
+ if (this.#isPunctuator(';')) {
708
+ this.#next();
709
+ continue;
710
+ }
711
+ const specifiers = this.#specifiers(false);
712
+ //An anonymous struct or union member: its members are reached through the name _0, _1…
713
+ if (this.#isPunctuator(';')) {
714
+ this.#next();
715
+ if (specifiers.base.kind === 'struct' || specifiers.base.kind === 'union')
716
+ members.push({ name: `_${anonymous++}`, type: specifiers.base, align: specifiers.align });
717
+ continue;
718
+ }
719
+ for (;;) {
720
+ let declarator;
721
+ if (!this.#isPunctuator(':'))
722
+ declarator = this.#declarator(false);
723
+ let bits;
724
+ if (this.#isPunctuator(':')) {
725
+ this.#next();
726
+ const width = this.#conditional();
727
+ bits = Number(width.value);
728
+ if (bits < 0)
729
+ throw this.#error('A bitfield has a negative width', this.#peek());
730
+ }
731
+ const memberAttributes = this.#attributes();
732
+ if (!declarator?.name && bits === undefined)
733
+ throw this.#error('A member name is expected', this.#peek());
734
+ const built = declarator ? declarator.build(specifiers.base, specifiers.constant).type : specifiers.base;
735
+ if (built.kind === 'function')
736
+ throw this.#error(`The member ${declarator?.name} is a function: a pointer to a function is expected`, declarator?.token ?? this.#peek());
737
+ if (built.kind === 'struct' || built.kind === 'union') {
738
+ if (!built.members)
739
+ throw this.#error(`The member ${declarator?.name} has the type ${typeText(built)}, declared but not defined`, declarator?.token ?? this.#peek());
740
+ }
741
+ const align = memberAttributes?.align ?? declarator?.align ?? specifiers.align;
742
+ members.push({ name: declarator?.name ?? '', type: built, ...bits !== undefined ? { bits: bits } : {}, ...align ? { align: align } : {} });
743
+ if (this.#isPunctuator(',')) {
744
+ this.#next();
745
+ continue;
746
+ }
747
+ this.#expectPunctuator(';');
748
+ break;
749
+ }
750
+ }
751
+ this.#next();
752
+ const after = this.#attributes();
753
+ const packed = !!(attributes?.packed || after?.packed);
754
+ record.members = members;
755
+ if (tag)
756
+ this.#defined(key);
757
+ if (packed)
758
+ record.pack = 1;
759
+ else if (this.#pack !== undefined)
760
+ record.pack = this.#pack;
761
+ const align = after?.align ?? attributes?.align;
762
+ if (align)
763
+ record.align = align;
764
+ return record;
765
+ }
766
+ /**
767
+ * An enumeration: its tag, or its definition (the values evaluated, the constants added)
768
+ */
769
+ #enumeration() {
770
+ const keyword = this.#next();
771
+ this.#attributes();
772
+ let tag;
773
+ if (this.#peek().kind === 'identifier' && !ATTRIBUTES.has(this.#peek().text))
774
+ tag = this.#next().text;
775
+ this.#attributes();
776
+ let base;
777
+ if (this.#isPunctuator(':')) {
778
+ this.#next();
779
+ const type = this.#specifiers(false).base;
780
+ if (type.kind !== 'primitive' || type.name === 'void' || type.name === 'float' || type.name === 'double')
781
+ throw this.#error('The type of an enumeration must be an integer type', keyword);
782
+ base = type.name;
783
+ }
784
+ const key = `enum ${tag}`;
785
+ if (!this.#isPunctuator('{')) {
786
+ const known = tag ? this.#declarations.tags.get(key) : undefined;
787
+ if (known?.kind === 'enum')
788
+ return known;
789
+ if (!tag)
790
+ throw this.#error('An enumeration without a name must be defined', keyword);
791
+ //Declared before its values (enum e : int;), or used before: its values are not known, its type is
792
+ const declared = { kind: 'enum', tag: tag, base: base ?? 'int', values: new Map() };
793
+ this.#declarations.tags.set(key, declared);
794
+ return declared;
795
+ }
796
+ this.#next();
797
+ const values = new Map();
798
+ let next = 0n;
799
+ while (!this.#isPunctuator('}')) {
800
+ const name = this.#next();
801
+ if (name.kind !== 'identifier')
802
+ throw this.#error(`An enumerator name is expected, not ${this.#describe(name)}`, name);
803
+ this.#attributes();
804
+ if (this.#isPunctuator('=')) {
805
+ this.#next();
806
+ next = this.#conditional().value;
807
+ }
808
+ values.set(name.text, next);
809
+ this.#declarations.constants.set(name.text, next);
810
+ this.#defined(`constant ${name.text}`);
811
+ next++;
812
+ if (this.#isPunctuator(','))
813
+ this.#next();
814
+ else if (!this.#isPunctuator('}'))
815
+ throw this.#error(`, or } is expected, not ${this.#describe(this.#peek())}`, this.#peek());
816
+ }
817
+ this.#next();
818
+ this.#attributes();
819
+ const type = { kind: 'enum', tag: tag, base: base ?? this.#enumBase([...values.values()]), values: values };
820
+ if (tag) {
821
+ this.#declarations.tags.set(key, type);
822
+ this.#defined(key);
823
+ }
824
+ return type;
825
+ }
826
+ /**
827
+ * The integer type of an enumeration without one: int, or the smallest type holding its values (GCC and Clang; the
828
+ * compiler of Microsoft keeps int)
829
+ * @param values
830
+ */
831
+ #enumBase(values) {
832
+ const negative = values.some((value) => value < 0n);
833
+ const fits = (min, max) => values.every((value) => value >= min && value <= max);
834
+ if (fits(-(2n ** 31n), 2n ** 31n - 1n))
835
+ return 'int';
836
+ if (!negative && fits(0n, 2n ** 32n - 1n))
837
+ return 'uint';
838
+ if (this.#platform.bitfields === 'msvc')
839
+ return 'int';
840
+ if (fits(-(2n ** 63n), 2n ** 63n - 1n))
841
+ return 'longlong';
842
+ return 'ulonglong';
843
+ }
844
+ /**
845
+ * A declarator: the pointers, the name (or none, abstract), the arrays and the parameters, in the order of C
846
+ * @param abstract whether it has no name (a type name, a parameter without name)
847
+ */
848
+ #declarator(abstract) {
849
+ //The pointers: each with its own constness
850
+ const pointers = [];
851
+ let convention;
852
+ let direction;
853
+ let align;
854
+ for (;;) {
855
+ const token = this.#peek();
856
+ if (this.#isPunctuator('*')) {
857
+ this.#next();
858
+ pointers.push(false);
859
+ continue;
860
+ }
861
+ if (token.kind !== 'identifier')
862
+ break;
863
+ if (CONSTS.has(token.text) && pointers.length) {
864
+ this.#next();
865
+ pointers[pointers.length - 1] = true;
866
+ continue;
867
+ }
868
+ if (CONSTS.has(token.text) || IGNORED.has(token.text)) {
869
+ this.#next();
870
+ continue;
871
+ }
872
+ if (CONVENTIONS.has(token.text)) {
873
+ this.#next();
874
+ convention = CONVENTIONS.get(token.text);
875
+ continue;
876
+ }
877
+ if (ATTRIBUTES.has(token.text)) {
878
+ align = this.#attributes()?.align ?? align;
879
+ continue;
880
+ }
881
+ const annotation = this.#typedef(token.text) ? undefined : annotationDirection(token.text);
882
+ if (annotation !== undefined) {
883
+ this.#next();
884
+ if (this.#isPunctuator('('))
885
+ this.#skipBalanced();
886
+ if (annotation)
887
+ direction = annotation;
888
+ continue;
889
+ }
890
+ break;
891
+ }
892
+ //The direct declarator: a name, a declarator in parentheses, or nothing
893
+ let name;
894
+ let token;
895
+ let nested;
896
+ if (this.#peek().kind === 'identifier') {
897
+ token = this.#next();
898
+ name = token.text;
899
+ }
900
+ else if (this.#isPunctuator('(') && this.#nestedDeclarator()) {
901
+ this.#next();
902
+ nested = this.#declarator(abstract);
903
+ this.#expectPunctuator(')');
904
+ }
905
+ //The arrays and the parameter lists after it
906
+ const suffixes = [];
907
+ for (;;) {
908
+ if (this.#isPunctuator('[')) {
909
+ this.#next();
910
+ if (this.#isPunctuator(']')) {
911
+ this.#next();
912
+ suffixes.push({ array: 0 });
913
+ continue;
914
+ }
915
+ //[static 10], [const], [*]
916
+ while (this.#peek().kind === 'identifier' && (this.#peek().text === 'static' || CONSTS.has(this.#peek().text) || IGNORED.has(this.#peek().text)))
917
+ this.#next();
918
+ const length = this.#conditional();
919
+ this.#expectPunctuator(']');
920
+ if (length.value < 0n)
921
+ throw this.#error('An array has a negative length', this.#peek());
922
+ suffixes.push({ array: Number(length.value) });
923
+ continue;
924
+ }
925
+ if (this.#isPunctuator('(')) {
926
+ suffixes.push({ function: this.#parameters() });
927
+ continue;
928
+ }
929
+ break;
930
+ }
931
+ while (this.#peek().kind === 'identifier' && (ATTRIBUTES.has(this.#peek().text) || IGNORED.has(this.#peek().text))) {
932
+ if (ATTRIBUTES.has(this.#peek().text))
933
+ align = this.#attributes()?.align ?? align;
934
+ else
935
+ this.#next();
936
+ }
937
+ let asm;
938
+ if (this.#peek().kind === 'identifier' && ASM.has(this.#peek().text))
939
+ asm = this.#asmLabel();
940
+ const functionConvention = nested?.convention ?? convention;
941
+ const declarator = {
942
+ name: name ?? nested?.name,
943
+ token: token ?? nested?.token,
944
+ convention: nested ? convention : undefined,
945
+ asm: asm ?? nested?.asm,
946
+ direction: direction ?? nested?.direction,
947
+ align: align ?? nested?.align,
948
+ build: (base, baseConst) => {
949
+ let type = base;
950
+ let targetConst = baseConst;
951
+ for (const own of pointers) {
952
+ type = { kind: 'pointer', to: type, constTarget: targetConst };
953
+ targetConst = own;
954
+ }
955
+ for (let i = suffixes.length - 1; i >= 0; i--) {
956
+ const suffix = suffixes[i];
957
+ if ('array' in suffix) {
958
+ if (type.kind === 'function')
959
+ throw this.#error('An array of functions is not allowed', token ?? this.#peek());
960
+ type = { kind: 'array', of: type, length: suffix.array };
961
+ }
962
+ else {
963
+ type = { kind: 'function', result: type, parameters: suffix.function.parameters, variadic: suffix.function.variadic, convention: (nested && !suffix.function.variadic ? functionConvention : undefined) ?? 'cdecl' };
964
+ }
965
+ }
966
+ if (nested)
967
+ return nested.build(type, targetConst && !suffixes.length);
968
+ return { type: type, constant: targetConst };
969
+ }
970
+ };
971
+ if (!nested && convention)
972
+ declarator.convention = convention;
973
+ return declarator;
974
+ }
975
+ /**
976
+ * Whether ( starts a declarator in parentheses ((*fp), (__stdcall *fp)) rather than a parameter list
977
+ */
978
+ #nestedDeclarator() {
979
+ const next = this.#peek(1);
980
+ if (next.kind === 'punctuator')
981
+ return next.text === '*' || next.text === '(' || next.text === '[';
982
+ if (next.kind !== 'identifier')
983
+ return false;
984
+ if (CONVENTIONS.has(next.text) || ATTRIBUTES.has(next.text))
985
+ return true;
986
+ if (TYPE_WORDS.has(next.text) || CONSTS.has(next.text) || ['struct', 'union', 'enum'].includes(next.text) || this.#typedef(next.text))
987
+ return false;
988
+ return annotationDirection(next.text) === undefined;
989
+ }
990
+ /**
991
+ * A parameter list: (void), (), or the parameters with an optional ...
992
+ */
993
+ #parameters() {
994
+ const open = this.#next();
995
+ const parameters = [];
996
+ let variadic = false;
997
+ if (this.#isPunctuator(')')) {
998
+ this.#next();
999
+ return { parameters: parameters, variadic: false };
1000
+ }
1001
+ //(void)
1002
+ if (this.#peek().text === 'void' && this.#isPunctuator(')', 1)) {
1003
+ this.#next();
1004
+ this.#next();
1005
+ return { parameters: parameters, variadic: false };
1006
+ }
1007
+ //A name that is not a type: a type not declared, or a declaration of the old style (names without types)
1008
+ if (this.#peek().kind === 'identifier' && !this.#startsType(this.#peek()) && (this.#isPunctuator(',', 1) || this.#isPunctuator(')', 1))) {
1009
+ throw this.#error(`Unknown type ${this.#peek().text} (a parameter needs a type: the declarations of the old style, names without types, are not supported)`, this.#peek());
1010
+ }
1011
+ for (;;) {
1012
+ if (this.#isPunctuator('...')) {
1013
+ this.#next();
1014
+ variadic = true;
1015
+ this.#expectPunctuator(')');
1016
+ break;
1017
+ }
1018
+ const specifiers = this.#specifiers(false);
1019
+ const declarator = this.#declarator(true);
1020
+ let { type } = declarator.build(specifiers.base, specifiers.constant);
1021
+ //Parameters of array and function types are pointers
1022
+ if (type.kind === 'array')
1023
+ type = { kind: 'pointer', to: type.of, constTarget: specifiers.constant };
1024
+ if (type.kind === 'function')
1025
+ type = { kind: 'pointer', to: type, constTarget: false };
1026
+ if (type.kind === 'primitive' && type.name === 'void')
1027
+ throw this.#error('A parameter cannot be void', open);
1028
+ parameters.push({ ...declarator.name ? { name: declarator.name } : {}, type: type, direction: declarator.direction ?? specifiers.direction ?? 'in' });
1029
+ if (this.#isPunctuator(',')) {
1030
+ this.#next();
1031
+ continue;
1032
+ }
1033
+ this.#expectPunctuator(')');
1034
+ break;
1035
+ }
1036
+ return { parameters: parameters, variadic: variadic };
1037
+ }
1038
+ /**
1039
+ * Whether a token starts a type
1040
+ * @param token
1041
+ */
1042
+ #startsType(token) {
1043
+ if (token.kind !== 'identifier')
1044
+ return false;
1045
+ const word = token.text;
1046
+ return TYPE_WORDS.has(word) || CONSTS.has(word) || IGNORED.has(word) || ['struct', 'union', 'enum'].includes(word) || !!this.#typedef(word) || annotationDirection(word) !== undefined;
1047
+ }
1048
+ /**
1049
+ * The attributes ahead (__attribute__((…)), __declspec(…)), the packed and aligned ones read, the others skipped
1050
+ */
1051
+ #attributes() {
1052
+ let result;
1053
+ while (this.#peek().kind === 'identifier' && ATTRIBUTES.has(this.#peek().text)) {
1054
+ const keyword = this.#next();
1055
+ if (!this.#isPunctuator('('))
1056
+ throw this.#error(`( is expected after ${keyword.text}`, keyword);
1057
+ const start = this.#index;
1058
+ this.#skipBalanced();
1059
+ const inner = this.#tokens.slice(start + 1, this.#index - 1);
1060
+ result ??= { packed: false };
1061
+ for (let i = 0; i < inner.length; i++) {
1062
+ const word = inner[i].text.replace(/^__(.*)__$/, '$1');
1063
+ if (word === 'packed')
1064
+ result.packed = true;
1065
+ if (word === 'aligned' || word === 'align') {
1066
+ if (inner[i + 1]?.text !== '(') {
1067
+ result.align = 16;
1068
+ }
1069
+ else {
1070
+ const value = inner[i + 2]?.value;
1071
+ if (value === undefined)
1072
+ throw this.#error(`The alignment of ${word} must be a number`, inner[i]);
1073
+ result.align = Number(value);
1074
+ }
1075
+ }
1076
+ }
1077
+ if (result.align !== undefined && ![1, 2, 4, 8].includes(result.align))
1078
+ throw this.#error(`An alignment of ${result.align} bytes is not supported (1, 2, 4 or 8)`, keyword);
1079
+ }
1080
+ return result;
1081
+ }
1082
+ /**
1083
+ * _Alignas(n), alignas(type)
1084
+ */
1085
+ #alignas() {
1086
+ const keyword = this.#next();
1087
+ this.#expectPunctuator('(');
1088
+ let align;
1089
+ if (this.#startsType(this.#peek())) {
1090
+ const specifiers = this.#specifiers(false);
1091
+ align = alignmentOf(this.#declarator(true).build(specifiers.base, specifiers.constant).type, this.#platform);
1092
+ }
1093
+ else {
1094
+ align = Number(this.#conditional().value);
1095
+ }
1096
+ this.#expectPunctuator(')');
1097
+ if (![1, 2, 4, 8].includes(align))
1098
+ throw this.#error(`An alignment of ${align} bytes is not supported (1, 2, 4 or 8)`, keyword);
1099
+ return align;
1100
+ }
1101
+ /**
1102
+ * The label of an __asm (the name of the symbol in the binary), its strings joined
1103
+ */
1104
+ #asmLabel() {
1105
+ if (this.#peek().kind !== 'identifier' || !ASM.has(this.#peek().text))
1106
+ return undefined;
1107
+ this.#next();
1108
+ this.#expectPunctuator('(');
1109
+ let label = '';
1110
+ while (this.#peek().kind === 'string')
1111
+ label += this.#next().text;
1112
+ this.#expectPunctuator(')');
1113
+ return label;
1114
+ }
1115
+ /**
1116
+ * The initializer of a variable: its value when it is a constant expression; a list in braces is skipped
1117
+ */
1118
+ #initializer() {
1119
+ if (this.#isPunctuator('{')) {
1120
+ this.#skipBalanced();
1121
+ return undefined;
1122
+ }
1123
+ const start = this.#index;
1124
+ try {
1125
+ return this.#conditional();
1126
+ }
1127
+ catch {
1128
+ //Not a constant expression: skipped to the end of the declarator
1129
+ this.#index = start;
1130
+ let depth = 0;
1131
+ while (!this.#at('end')) {
1132
+ const token = this.#peek();
1133
+ if (token.kind === 'punctuator' && '([{'.includes(token.text))
1134
+ depth++;
1135
+ if (token.kind === 'punctuator' && ')]}'.includes(token.text))
1136
+ depth--;
1137
+ if (depth <= 0 && token.kind === 'punctuator' && (token.text === ',' || token.text === ';'))
1138
+ break;
1139
+ this.#next();
1140
+ }
1141
+ return undefined;
1142
+ }
1143
+ }
1144
+ /**
1145
+ * A constant expression, with the comma operator
1146
+ */
1147
+ #expression() {
1148
+ let value = this.#conditional();
1149
+ while (this.#isPunctuator(',')) {
1150
+ this.#next();
1151
+ value = this.#conditional();
1152
+ }
1153
+ return value;
1154
+ }
1155
+ /**
1156
+ * A conditional expression: a ? b : c
1157
+ */
1158
+ #conditional() {
1159
+ const condition = this.#binary(0);
1160
+ if (!this.#isPunctuator('?'))
1161
+ return condition;
1162
+ this.#next();
1163
+ const yes = this.#expression();
1164
+ this.#expectPunctuator(':');
1165
+ const no = this.#conditional();
1166
+ return condition.value !== 0n ? yes : no;
1167
+ }
1168
+ /**
1169
+ * The binary operators, by precedence climbing
1170
+ * @param minimum the lowest precedence read
1171
+ */
1172
+ #binary(minimum) {
1173
+ let left = this.#unary();
1174
+ for (;;) {
1175
+ const token = this.#peek();
1176
+ const precedence = token.kind === 'punctuator' ? PRECEDENCE[token.text] : undefined;
1177
+ if (precedence === undefined || precedence < minimum)
1178
+ return left;
1179
+ this.#next();
1180
+ const right = this.#binary(precedence + 1);
1181
+ left = this.#apply(token, left, right);
1182
+ }
1183
+ }
1184
+ /**
1185
+ * A binary operator applied, with the conversions of C (the wider and the unsigned type win)
1186
+ * @param operator
1187
+ * @param left
1188
+ * @param right
1189
+ */
1190
+ #apply(operator, left, right) {
1191
+ const bits = Math.max(left.bits, right.bits);
1192
+ const unsigned = (left.unsigned && left.bits >= right.bits) || (right.unsigned && right.bits >= left.bits);
1193
+ const a = left.value;
1194
+ const b = right.value;
1195
+ const result = (value, type = { unsigned: unsigned, bits: bits }) => this.#wrap(value, type.unsigned, type.bits);
1196
+ const truth = (value) => ({ value: value ? 1n : 0n, unsigned: false, bits: 32 });
1197
+ switch (operator.text) {
1198
+ case '*':
1199
+ return result(a * b);
1200
+ case '/':
1201
+ case '%':
1202
+ if (b === 0n)
1203
+ throw this.#error('Division by zero in a constant expression', operator);
1204
+ return result(operator.text === '/' ? a / b : a % b);
1205
+ case '+':
1206
+ return result(a + b);
1207
+ case '-':
1208
+ return result(a - b);
1209
+ case '<<':
1210
+ return result(a << b, left);
1211
+ case '>>':
1212
+ return result(a >> b, left);
1213
+ case '<':
1214
+ return truth(a < b);
1215
+ case '>':
1216
+ return truth(a > b);
1217
+ case '<=':
1218
+ return truth(a <= b);
1219
+ case '>=':
1220
+ return truth(a >= b);
1221
+ case '==':
1222
+ return truth(a === b);
1223
+ case '!=':
1224
+ return truth(a !== b);
1225
+ case '&':
1226
+ return result(a & b);
1227
+ case '^':
1228
+ return result(a ^ b);
1229
+ case '|':
1230
+ return result(a | b);
1231
+ case '&&':
1232
+ return truth(a !== 0n && b !== 0n);
1233
+ case '||':
1234
+ return truth(a !== 0n || b !== 0n);
1235
+ }
1236
+ throw this.#error(`The operator ${operator.text} is not allowed in a constant expression`, operator);
1237
+ }
1238
+ /**
1239
+ * An integer wrapped to its type: the unsigned ones modulo 2^bits
1240
+ * @param value
1241
+ * @param unsigned
1242
+ * @param bits
1243
+ */
1244
+ #wrap(value, unsigned, bits) {
1245
+ if (unsigned)
1246
+ return { value: BigInt.asUintN(bits, value), unsigned: true, bits: bits };
1247
+ return { value: value, unsigned: false, bits: bits };
1248
+ }
1249
+ /**
1250
+ * The unary operators, the casts, sizeof and _Alignof
1251
+ */
1252
+ #unary() {
1253
+ const token = this.#peek();
1254
+ if (token.kind === 'punctuator') {
1255
+ if (['+', '-', '~', '!'].includes(token.text)) {
1256
+ this.#next();
1257
+ const operand = this.#unary();
1258
+ switch (token.text) {
1259
+ case '+':
1260
+ return operand;
1261
+ case '-':
1262
+ return this.#wrap(-operand.value, operand.unsigned, operand.bits);
1263
+ case '~':
1264
+ return this.#wrap(~operand.value, operand.unsigned, operand.bits);
1265
+ default:
1266
+ return { value: operand.value === 0n ? 1n : 0n, unsigned: false, bits: 32 };
1267
+ }
1268
+ }
1269
+ //A cast: (type) operand
1270
+ if (token.text === '(' && this.#startsType(this.#peek(1))) {
1271
+ this.#next();
1272
+ const type = this.#typeInExpression();
1273
+ this.#expectPunctuator(')');
1274
+ return this.#cast(this.#unary(), type, token);
1275
+ }
1276
+ }
1277
+ if (token.kind === 'identifier' && (token.text === 'sizeof' || ['_Alignof', 'alignof', '__alignof__', '__alignof'].includes(token.text))) {
1278
+ this.#next();
1279
+ this.#expectPunctuator('(');
1280
+ if (!this.#startsType(this.#peek()))
1281
+ throw this.#error(`${token.text} of an expression is not supported: give a type`, token);
1282
+ const type = this.#typeInExpression();
1283
+ this.#expectPunctuator(')');
1284
+ const value = token.text === 'sizeof' ? sizeOf(type, this.#platform) : alignmentOf(type, this.#platform);
1285
+ return { value: BigInt(value), unsigned: true, bits: this.#platform.pointerSize * 8 };
1286
+ }
1287
+ return this.#primary();
1288
+ }
1289
+ /**
1290
+ * A type name inside an expression (a cast, sizeof)
1291
+ */
1292
+ #typeInExpression() {
1293
+ const specifiers = this.#specifiers(false);
1294
+ return this.#declarator(true).build(specifiers.base, specifiers.constant).type;
1295
+ }
1296
+ /**
1297
+ * A value converted to an integer type
1298
+ * @param value
1299
+ * @param type
1300
+ * @param token
1301
+ */
1302
+ #cast(value, type, token) {
1303
+ const name = type.kind === 'primitive' ? type.name : type.kind === 'enum' ? type.base : undefined;
1304
+ if (type.kind === 'pointer')
1305
+ return this.#wrap(value.value, true, this.#platform.pointerSize * 8);
1306
+ if (!name || name === 'void' || name === 'float' || name === 'double')
1307
+ throw this.#error(`A cast to ${typeText(type)} is not allowed in a constant expression`, token);
1308
+ const bits = primitiveSize(name, this.#platform) * 8;
1309
+ if (name === 'bool')
1310
+ return { value: value.value === 0n ? 0n : 1n, unsigned: true, bits: 8 };
1311
+ const unsigned = !primitiveSigned(name, this.#platform);
1312
+ return unsigned ? this.#wrap(value.value, true, bits) : { value: BigInt.asIntN(bits, value.value), unsigned: false, bits: bits };
1313
+ }
1314
+ /**
1315
+ * A number, a character, a constant, or an expression in parentheses
1316
+ */
1317
+ #primary() {
1318
+ const token = this.#next();
1319
+ if (token.kind === 'number') {
1320
+ if (token.float)
1321
+ throw this.#error(`The floating-point number ${token.text} is not allowed in an integer constant expression`, token);
1322
+ return this.#literal(token);
1323
+ }
1324
+ if (token.kind === 'char')
1325
+ return { value: token.value, unsigned: false, bits: 32 };
1326
+ if (token.kind === 'identifier') {
1327
+ const value = this.#declarations.constants.get(token.text);
1328
+ if (value === undefined)
1329
+ throw this.#error(`Unknown constant ${token.text}: a macro is not known (write its value)`, token);
1330
+ return { value: value, unsigned: value > 2n ** 31n - 1n, bits: value > 2n ** 32n - 1n || value < -(2n ** 31n) ? 64 : 32 };
1331
+ }
1332
+ if (token.kind === 'punctuator' && token.text === '(') {
1333
+ const value = this.#expression();
1334
+ this.#expectPunctuator(')');
1335
+ return value;
1336
+ }
1337
+ throw this.#error(`A constant expression is expected, not ${this.#describe(token)}`, token);
1338
+ }
1339
+ /**
1340
+ * The type C gives an integer literal: int, then wider or unsigned as its value and its suffix ask
1341
+ * @param token
1342
+ */
1343
+ #literal(token) {
1344
+ const value = token.value;
1345
+ const suffix = token.text.match(/[uUlL]+$/)?.[0].toLowerCase() ?? '';
1346
+ const unsignedSuffix = suffix.includes('u');
1347
+ const longs = (suffix.match(/l/g) ?? []).length;
1348
+ const decimal = !/^0/.test(token.text) || token.text === '0';
1349
+ const longBits = this.#platform.longSize * 8;
1350
+ const candidates = [];
1351
+ if (longs === 0)
1352
+ candidates.push({ bits: 32, unsigned: false }, { bits: 32, unsigned: true });
1353
+ if (longs <= 1)
1354
+ candidates.push({ bits: longBits, unsigned: false }, { bits: longBits, unsigned: true });
1355
+ candidates.push({ bits: 64, unsigned: false }, { bits: 64, unsigned: true });
1356
+ for (const candidate of candidates) {
1357
+ if (unsignedSuffix && !candidate.unsigned)
1358
+ continue;
1359
+ //A decimal literal without u is never unsigned
1360
+ if (decimal && !unsignedSuffix && candidate.unsigned)
1361
+ continue;
1362
+ const max = candidate.unsigned ? 2n ** BigInt(candidate.bits) - 1n : 2n ** BigInt(candidate.bits - 1) - 1n;
1363
+ if (value <= max)
1364
+ return { value: value, unsigned: candidate.unsigned, bits: candidate.bits };
1365
+ }
1366
+ throw this.#error(`The number ${token.text} is too large`, token);
1367
+ }
1368
+ /**
1369
+ * Skip balanced brackets from the one ahead: (…), […], {…}
1370
+ */
1371
+ #skipBalanced() {
1372
+ const open = this.#next();
1373
+ const pairs = { '(': ')', '[': ']', '{': '}' };
1374
+ const stack = [pairs[open.text]];
1375
+ while (stack.length) {
1376
+ const token = this.#next();
1377
+ if (token.kind === 'end')
1378
+ throw this.#error(`${open.text} is not closed`, open);
1379
+ if (token.kind !== 'punctuator')
1380
+ continue;
1381
+ if (pairs[token.text])
1382
+ stack.push(pairs[token.text]);
1383
+ else if (token.text === stack[stack.length - 1])
1384
+ stack.pop();
1385
+ }
1386
+ }
1387
+ #peek(ahead = 0) {
1388
+ return this.#tokens[Math.min(this.#index + ahead, this.#tokens.length - 1)];
1389
+ }
1390
+ #next() {
1391
+ const token = this.#peek();
1392
+ if (this.#index < this.#tokens.length - 1)
1393
+ this.#index++;
1394
+ return token;
1395
+ }
1396
+ #at(kind) {
1397
+ return this.#peek().kind === kind;
1398
+ }
1399
+ #isPunctuator(text, ahead = 0) {
1400
+ const token = this.#peek(ahead);
1401
+ return token.kind === 'punctuator' && token.text === text;
1402
+ }
1403
+ #expectPunctuator(text) {
1404
+ if (!this.#isPunctuator(text))
1405
+ throw this.#error(`${text} is expected, not ${this.#describe(this.#peek())}`, this.#peek());
1406
+ return this.#next();
1407
+ }
1408
+ #describe(token) {
1409
+ return token.kind === 'end' ? 'the end' : JSON.stringify(token.text);
1410
+ }
1411
+ #error(message, token) {
1412
+ return declarationError(message, token.line, token.column);
1413
+ }
1414
+ }
1415
+ /**
1416
+ * The precedence of the binary operators (the higher, the tighter)
1417
+ */
1418
+ const PRECEDENCE = {
1419
+ '||': 1, '&&': 2, '|': 3, '^': 4, '&': 5, '==': 6, '!=': 6, '<': 7, '>': 7, '<=': 7, '>=': 7, '<<': 8, '>>': 8,
1420
+ '+': 9, '-': 9, '*': 10, '/': 10, '%': 10
1421
+ };