@lakutata/ffi 0.0.0-stage → 3.1.5
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/LICENSE +23 -0
- package/README.md +163 -2
- package/README.zh-CN.md +140 -0
- package/dist/cjs/components/NativeLibrary.d.ts +229 -0
- package/dist/cjs/components/NativeLibrary.js +346 -0
- package/dist/cjs/decorators/Library.d.ts +61 -0
- package/dist/cjs/decorators/Library.js +44 -0
- package/dist/cjs/decorators/Native.d.ts +69 -0
- package/dist/cjs/decorators/Native.js +90 -0
- package/dist/cjs/exceptions/NativeDeclarationException.d.ts +12 -0
- package/dist/cjs/exceptions/NativeDeclarationException.js +19 -0
- package/dist/cjs/exceptions/NativeLibraryClosedException.d.ts +12 -0
- package/dist/cjs/exceptions/NativeLibraryClosedException.js +19 -0
- package/dist/cjs/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
- package/dist/cjs/exceptions/NativeLibraryNotFoundException.js +18 -0
- package/dist/cjs/exceptions/NativeMemoryException.d.ts +11 -0
- package/dist/cjs/exceptions/NativeMemoryException.js +18 -0
- package/dist/cjs/exports/FFI.d.ts +12 -0
- package/dist/cjs/exports/FFI.js +40 -0
- package/dist/cjs/exports/Generator.d.ts +2 -0
- package/dist/cjs/exports/Generator.js +10 -0
- package/dist/cjs/lib/Memory.d.ts +188 -0
- package/dist/cjs/lib/Memory.js +227 -0
- package/dist/cjs/lib/Runtime.d.ts +98 -0
- package/dist/cjs/lib/Runtime.js +209 -0
- package/dist/cjs/lib/backend/Functions.d.ts +65 -0
- package/dist/cjs/lib/backend/Functions.js +214 -0
- package/dist/cjs/lib/backend/KoffiTypes.d.ts +53 -0
- package/dist/cjs/lib/backend/KoffiTypes.js +426 -0
- package/dist/cjs/lib/declarations/Builtins.d.ts +33 -0
- package/dist/cjs/lib/declarations/Builtins.js +179 -0
- package/dist/cjs/lib/declarations/Layout.d.ts +41 -0
- package/dist/cjs/lib/declarations/Layout.js +180 -0
- package/dist/cjs/lib/declarations/Lexer.d.ts +40 -0
- package/dist/cjs/lib/declarations/Lexer.js +325 -0
- package/dist/cjs/lib/declarations/Parser.d.ts +90 -0
- package/dist/cjs/lib/declarations/Parser.js +1425 -0
- package/dist/cjs/lib/declarations/Platform.d.ts +50 -0
- package/dist/cjs/lib/declarations/Platform.js +27 -0
- package/dist/cjs/lib/declarations/Types.d.ts +173 -0
- package/dist/cjs/lib/declarations/Types.js +179 -0
- package/dist/cjs/lib/generator/CText.d.ts +14 -0
- package/dist/cjs/lib/generator/CText.js +133 -0
- package/dist/cjs/lib/generator/Generator.d.ts +136 -0
- package/dist/cjs/lib/generator/Generator.js +395 -0
- package/dist/cjs/lib/generator/Preprocessor.d.ts +60 -0
- package/dist/cjs/lib/generator/Preprocessor.js +94 -0
- package/dist/cjs/lib/generator/TypeScriptTypes.d.ts +35 -0
- package/dist/cjs/lib/generator/TypeScriptTypes.js +153 -0
- package/dist/cjs/lib/library/NativeCallback.d.ts +44 -0
- package/dist/cjs/lib/library/NativeCallback.js +66 -0
- package/dist/cjs/lib/library/NativeLibraryHandle.d.ts +268 -0
- package/dist/cjs/lib/library/NativeLibraryHandle.js +540 -0
- package/dist/cjs/lib/library/Resolve.d.ts +49 -0
- package/dist/cjs/lib/library/Resolve.js +130 -0
- package/dist/cjs/lib/thread/NativeThreads.d.ts +80 -0
- package/dist/cjs/lib/thread/NativeThreads.js +384 -0
- package/dist/cjs/lib/thread/NativeWorker.d.ts +6 -0
- package/dist/cjs/lib/thread/NativeWorker.js +96 -0
- package/dist/cjs/lib/thread/NativeWorkerCode.d.ts +5 -0
- package/dist/cjs/lib/thread/NativeWorkerCode.js +4 -0
- package/dist/cjs/lib/thread/PackageRequire.d.ts +6 -0
- package/dist/cjs/lib/thread/PackageRequire.js +4 -0
- package/dist/cjs/lib/thread/ThreadProtocol.d.ts +77 -0
- package/dist/cjs/lib/thread/ThreadProtocol.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/components/NativeLibrary.js +338 -0
- package/dist/esm/decorators/Library.js +40 -0
- package/dist/esm/decorators/Native.js +85 -0
- package/dist/esm/exceptions/NativeDeclarationException.js +15 -0
- package/dist/esm/exceptions/NativeLibraryClosedException.js +15 -0
- package/dist/esm/exceptions/NativeLibraryNotFoundException.js +14 -0
- package/dist/esm/exceptions/NativeMemoryException.js +14 -0
- package/dist/esm/exports/FFI.js +10 -0
- package/dist/esm/exports/Generator.js +2 -0
- package/dist/esm/lib/Memory.js +210 -0
- package/dist/esm/lib/Runtime.js +192 -0
- package/dist/esm/lib/backend/Functions.js +206 -0
- package/dist/esm/lib/backend/KoffiTypes.js +418 -0
- package/dist/esm/lib/declarations/Builtins.js +174 -0
- package/dist/esm/lib/declarations/Layout.js +175 -0
- package/dist/esm/lib/declarations/Lexer.js +321 -0
- package/dist/esm/lib/declarations/Parser.js +1421 -0
- package/dist/esm/lib/declarations/Platform.js +23 -0
- package/dist/esm/lib/declarations/Types.js +170 -0
- package/dist/esm/lib/generator/CText.js +129 -0
- package/dist/esm/lib/generator/Generator.js +388 -0
- package/dist/esm/lib/generator/Preprocessor.js +87 -0
- package/dist/esm/lib/generator/TypeScriptTypes.js +149 -0
- package/dist/esm/lib/library/NativeCallback.js +59 -0
- package/dist/esm/lib/library/NativeLibraryHandle.js +532 -0
- package/dist/esm/lib/library/Resolve.js +122 -0
- package/dist/esm/lib/thread/NativeThreads.js +377 -0
- package/dist/esm/lib/thread/NativeWorker.js +93 -0
- package/dist/esm/lib/thread/NativeWorkerCode.js +1 -0
- package/dist/esm/lib/thread/PackageRequire.js +2 -0
- package/dist/esm/lib/thread/ThreadProtocol.js +1 -0
- package/dist/types/components/NativeLibrary.d.ts +229 -0
- package/dist/types/decorators/Library.d.ts +61 -0
- package/dist/types/decorators/Native.d.ts +69 -0
- package/dist/types/exceptions/NativeDeclarationException.d.ts +12 -0
- package/dist/types/exceptions/NativeLibraryClosedException.d.ts +12 -0
- package/dist/types/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
- package/dist/types/exceptions/NativeMemoryException.d.ts +11 -0
- package/dist/types/exports/FFI.d.ts +12 -0
- package/dist/types/exports/Generator.d.ts +2 -0
- package/dist/types/lib/Memory.d.ts +188 -0
- package/dist/types/lib/Runtime.d.ts +98 -0
- package/dist/types/lib/backend/Functions.d.ts +65 -0
- package/dist/types/lib/backend/KoffiTypes.d.ts +53 -0
- package/dist/types/lib/declarations/Builtins.d.ts +33 -0
- package/dist/types/lib/declarations/Layout.d.ts +41 -0
- package/dist/types/lib/declarations/Lexer.d.ts +40 -0
- package/dist/types/lib/declarations/Parser.d.ts +90 -0
- package/dist/types/lib/declarations/Platform.d.ts +50 -0
- package/dist/types/lib/declarations/Types.d.ts +173 -0
- package/dist/types/lib/generator/CText.d.ts +14 -0
- package/dist/types/lib/generator/Generator.d.ts +136 -0
- package/dist/types/lib/generator/Preprocessor.d.ts +60 -0
- package/dist/types/lib/generator/TypeScriptTypes.d.ts +35 -0
- package/dist/types/lib/library/NativeCallback.d.ts +44 -0
- package/dist/types/lib/library/NativeLibraryHandle.d.ts +268 -0
- package/dist/types/lib/library/Resolve.d.ts +49 -0
- package/dist/types/lib/thread/NativeThreads.d.ts +80 -0
- package/dist/types/lib/thread/NativeWorker.d.ts +6 -0
- package/dist/types/lib/thread/NativeWorkerCode.d.ts +5 -0
- package/dist/types/lib/thread/PackageRequire.d.ts +6 -0
- package/dist/types/lib/thread/ThreadProtocol.d.ts +77 -0
- 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
|
+
};
|