@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,175 @@
|
|
|
1
|
+
import { NativeDeclarationException } from '../../exceptions/NativeDeclarationException.js';
|
|
2
|
+
import { primitiveSize, typeText } from './Types.js';
|
|
3
|
+
const layouts = new WeakMap();
|
|
4
|
+
/**
|
|
5
|
+
* The size of a type, in bytes
|
|
6
|
+
* @param type
|
|
7
|
+
* @param platform
|
|
8
|
+
*/
|
|
9
|
+
export function sizeOf(type, platform) {
|
|
10
|
+
switch (type.kind) {
|
|
11
|
+
case 'primitive':
|
|
12
|
+
return primitiveSize(type.name, platform);
|
|
13
|
+
case 'pointer':
|
|
14
|
+
return platform.pointerSize;
|
|
15
|
+
case 'array':
|
|
16
|
+
return sizeOf(type.of, platform) * type.length;
|
|
17
|
+
case 'enum':
|
|
18
|
+
return primitiveSize(type.base, platform);
|
|
19
|
+
case 'struct':
|
|
20
|
+
case 'union':
|
|
21
|
+
return layoutOf(type, platform).size;
|
|
22
|
+
case 'function':
|
|
23
|
+
throw new NativeDeclarationException(`A function has no size: ${typeText(type)}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The alignment of a type, in bytes
|
|
28
|
+
* @param type
|
|
29
|
+
* @param platform
|
|
30
|
+
*/
|
|
31
|
+
export function alignmentOf(type, platform) {
|
|
32
|
+
switch (type.kind) {
|
|
33
|
+
case 'primitive':
|
|
34
|
+
case 'enum': {
|
|
35
|
+
const size = primitiveSize(type.kind === 'primitive' ? type.name : type.base, platform);
|
|
36
|
+
return size === 8 ? platform.int64Alignment : Math.max(size, 1);
|
|
37
|
+
}
|
|
38
|
+
case 'pointer':
|
|
39
|
+
return platform.pointerSize;
|
|
40
|
+
case 'array':
|
|
41
|
+
return alignmentOf(type.of, platform);
|
|
42
|
+
case 'struct':
|
|
43
|
+
case 'union':
|
|
44
|
+
return layoutOf(type, platform).alignment;
|
|
45
|
+
case 'function':
|
|
46
|
+
throw new NativeDeclarationException(`A function has no alignment: ${typeText(type)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Round up to a multiple
|
|
51
|
+
* @param value
|
|
52
|
+
* @param multiple
|
|
53
|
+
*/
|
|
54
|
+
function alignUp(value, multiple) {
|
|
55
|
+
return Math.ceil(value / multiple) * multiple;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The layout of a struct or of a union, as the compilers of the platform lay it out (GCC and Clang, or Microsoft's for
|
|
59
|
+
* the bitfields): the alignment of each member capped by #pragma pack, raised by its aligned attribute
|
|
60
|
+
* @param record
|
|
61
|
+
* @param platform
|
|
62
|
+
*/
|
|
63
|
+
export function layoutOf(record, platform) {
|
|
64
|
+
let cached = layouts.get(record);
|
|
65
|
+
const found = cached?.get(platform);
|
|
66
|
+
if (found)
|
|
67
|
+
return found;
|
|
68
|
+
if (!record.members)
|
|
69
|
+
throw new NativeDeclarationException(`The ${record.kind} ${record.tag ?? '(anonymous)'} is declared but not defined: it is used through pointers only`);
|
|
70
|
+
const layout = record.kind === 'union' ? unionLayout(record, platform) : structLayout(record, platform);
|
|
71
|
+
if (!cached)
|
|
72
|
+
layouts.set(record, cached = new Map());
|
|
73
|
+
cached.set(platform, layout);
|
|
74
|
+
return layout;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The alignment of a member: its type's, raised by its aligned attribute, capped by the packing of its struct
|
|
78
|
+
* @param member
|
|
79
|
+
* @param record
|
|
80
|
+
* @param platform
|
|
81
|
+
*/
|
|
82
|
+
function memberAlignment(member, record, platform) {
|
|
83
|
+
const natural = Math.max(alignmentOf(member.type, platform), member.align ?? 1);
|
|
84
|
+
return record.pack ? Math.min(natural, record.pack) : natural;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The layout of a union: every member at 0
|
|
88
|
+
* @param record
|
|
89
|
+
* @param platform
|
|
90
|
+
*/
|
|
91
|
+
function unionLayout(record, platform) {
|
|
92
|
+
let size = 0;
|
|
93
|
+
let alignment = record.align ?? 1;
|
|
94
|
+
const members = [];
|
|
95
|
+
for (const member of record.members) {
|
|
96
|
+
if (member.bits !== undefined)
|
|
97
|
+
throw new NativeDeclarationException(`The bitfield ${member.name || '(unnamed)'} of a union is not supported`);
|
|
98
|
+
const memberSize = sizeOf(member.type, platform);
|
|
99
|
+
alignment = Math.max(alignment, memberAlignment(member, record, platform));
|
|
100
|
+
size = Math.max(size, memberSize);
|
|
101
|
+
members.push({ member: member, offset: 0, size: memberSize });
|
|
102
|
+
}
|
|
103
|
+
return { size: alignUp(size, alignment), alignment: alignment, members: members, bitfields: false };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The layout of a struct: the members one after the other, each at its alignment; the bitfields in the storage units of
|
|
107
|
+
* their types, by the rules of the platform
|
|
108
|
+
* @param record
|
|
109
|
+
* @param platform
|
|
110
|
+
*/
|
|
111
|
+
function structLayout(record, platform) {
|
|
112
|
+
let bit = 0;
|
|
113
|
+
let alignment = record.align ?? 1;
|
|
114
|
+
let bitfields = false;
|
|
115
|
+
//The storage unit of the bitfields being filled (Microsoft's rules): its offset in bytes, its size, the bits used
|
|
116
|
+
let unit;
|
|
117
|
+
const members = [];
|
|
118
|
+
for (const member of record.members) {
|
|
119
|
+
if (member.bits === undefined) {
|
|
120
|
+
unit = undefined;
|
|
121
|
+
const aligned = memberAlignment(member, record, platform);
|
|
122
|
+
const offset = alignUp(Math.ceil(bit / 8), aligned);
|
|
123
|
+
const size = sizeOf(member.type, platform);
|
|
124
|
+
members.push({ member: member, offset: offset, size: size });
|
|
125
|
+
bit = (offset + size) * 8;
|
|
126
|
+
alignment = Math.max(alignment, aligned);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
bitfields = true;
|
|
130
|
+
if (record.pack !== undefined)
|
|
131
|
+
throw new NativeDeclarationException(`The bitfield ${member.name || '(unnamed)'} of a packed struct is not supported`);
|
|
132
|
+
if (member.type.kind !== 'primitive' && member.type.kind !== 'enum')
|
|
133
|
+
throw new NativeDeclarationException(`The bitfield ${member.name || '(unnamed)'} must have an integer type`);
|
|
134
|
+
const size = sizeOf(member.type, platform);
|
|
135
|
+
const typeAlignment = alignmentOf(member.type, platform);
|
|
136
|
+
const width = member.bits;
|
|
137
|
+
if (width > size * 8)
|
|
138
|
+
throw new NativeDeclarationException(`The bitfield ${member.name || '(unnamed)'} is wider than its type (${width} bits)`);
|
|
139
|
+
if (platform.bitfields === 'msvc') {
|
|
140
|
+
if (width === 0) {
|
|
141
|
+
//After a bitfield: ends its storage unit, the next member at the alignment of its type; ignored otherwise
|
|
142
|
+
if (unit) {
|
|
143
|
+
bit = alignUp((unit.offset + unit.size) * 8, typeAlignment * 8);
|
|
144
|
+
alignment = Math.max(alignment, typeAlignment);
|
|
145
|
+
}
|
|
146
|
+
unit = undefined;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!unit || unit.size !== size || unit.used + width > size * 8) {
|
|
150
|
+
const start = unit ? unit.offset + unit.size : Math.ceil(bit / 8);
|
|
151
|
+
unit = { offset: alignUp(start, typeAlignment), size: size, used: 0 };
|
|
152
|
+
alignment = Math.max(alignment, typeAlignment);
|
|
153
|
+
}
|
|
154
|
+
members.push({ member: member, offset: unit.offset, size: size, bitOffset: unit.offset * 8 + unit.used, bits: width });
|
|
155
|
+
unit.used += width;
|
|
156
|
+
bit = (unit.offset + unit.size) * 8;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
//GCC and Clang: in the first unit of the size of its type where it fits, from the current bit. The type of an
|
|
160
|
+
//unnamed bitfield aligns the struct by the rules of ARM only
|
|
161
|
+
if (member.name || platform.bitfields === 'aapcs')
|
|
162
|
+
alignment = Math.max(alignment, typeAlignment);
|
|
163
|
+
if (width === 0) {
|
|
164
|
+
bit = alignUp(bit, typeAlignment * 8);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if ((bit % (size * 8)) + width > size * 8)
|
|
168
|
+
bit = alignUp(bit, typeAlignment * 8);
|
|
169
|
+
members.push({ member: member, offset: Math.floor(bit / (size * 8)) * size, size: size, bitOffset: bit, bits: width });
|
|
170
|
+
bit += width;
|
|
171
|
+
}
|
|
172
|
+
if (unit)
|
|
173
|
+
bit = (unit.offset + unit.size) * 8;
|
|
174
|
+
return { size: alignUp(Math.ceil(bit / 8), alignment), alignment: alignment, members: members, bitfields: bitfields };
|
|
175
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { NativeDeclarationException } from '../../exceptions/NativeDeclarationException.js';
|
|
2
|
+
const PUNCTUATORS = ['...', '<<=', '>>=', '<<', '>>', '<=', '>=', '==', '!=', '&&', '||', '->', '++', '--', '##', '::'];
|
|
3
|
+
const SINGLE = '{}()[];,*:=+-/%&|^~!?<>.#';
|
|
4
|
+
const ESCAPES = { n: 10, t: 9, r: 13, '0': 0, a: 7, b: 8, f: 12, v: 11, '\\': 92, '\'': 39, '"': 34, '?': 63, e: 27 };
|
|
5
|
+
/**
|
|
6
|
+
* The directives of the preprocessor that change what is declared: the declarations given must be preprocessed
|
|
7
|
+
*/
|
|
8
|
+
const CONDITIONALS = ['if', 'ifdef', 'ifndef', 'elif', 'elifdef', 'elifndef', 'else', 'endif'];
|
|
9
|
+
/**
|
|
10
|
+
* The end of a directive of the preprocessor: the end of its line, its continued lines and the block comments that go on
|
|
11
|
+
* past it included (not the ones inside its strings)
|
|
12
|
+
* @param text
|
|
13
|
+
* @param start
|
|
14
|
+
*/
|
|
15
|
+
function directiveEnd(text, start) {
|
|
16
|
+
let end = start;
|
|
17
|
+
while (end < text.length && text[end] !== '\n') {
|
|
18
|
+
const char = text[end];
|
|
19
|
+
if (char === '\\' && text[end + 1] === '\n') {
|
|
20
|
+
end += 2;
|
|
21
|
+
}
|
|
22
|
+
else if (char === '\\' && text[end + 1] === '\r' && text[end + 2] === '\n') {
|
|
23
|
+
end += 3;
|
|
24
|
+
}
|
|
25
|
+
else if (char === '/' && text[end + 1] === '*') {
|
|
26
|
+
const close = text.indexOf('*/', end + 2);
|
|
27
|
+
end = close < 0 ? text.length : close + 2;
|
|
28
|
+
}
|
|
29
|
+
else if (char === '/' && text[end + 1] === '/') {
|
|
30
|
+
while (end < text.length && text[end] !== '\n')
|
|
31
|
+
end++;
|
|
32
|
+
}
|
|
33
|
+
else if (char === '"' || char === '\'') {
|
|
34
|
+
end++;
|
|
35
|
+
while (end < text.length && text[end] !== char && text[end] !== '\n')
|
|
36
|
+
end += text[end] === '\\' ? 2 : 1;
|
|
37
|
+
if (text[end] === char)
|
|
38
|
+
end++;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
end++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return end;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The text of a directive without its comments (the ones inside its strings and characters kept), its continued lines
|
|
48
|
+
* joined
|
|
49
|
+
* @param text
|
|
50
|
+
*/
|
|
51
|
+
function withoutComments(text) {
|
|
52
|
+
let result = '';
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < text.length) {
|
|
55
|
+
const char = text[i];
|
|
56
|
+
if (char === '/' && text[i + 1] === '*') {
|
|
57
|
+
const close = text.indexOf('*/', i + 2);
|
|
58
|
+
i = close < 0 ? text.length : close + 2;
|
|
59
|
+
result += ' ';
|
|
60
|
+
}
|
|
61
|
+
else if (char === '/' && text[i + 1] === '/') {
|
|
62
|
+
while (i < text.length && text[i] !== '\n')
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
else if (char === '"' || char === '\'') {
|
|
66
|
+
let end = i + 1;
|
|
67
|
+
while (end < text.length && text[end] !== char && text[end] !== '\n')
|
|
68
|
+
end += text[end] === '\\' ? 2 : 1;
|
|
69
|
+
result += text.slice(i, Math.min(end + 1, text.length));
|
|
70
|
+
i = end + 1;
|
|
71
|
+
}
|
|
72
|
+
else if (char === '\\' && (text[i + 1] === '\n' || (text[i + 1] === '\r' && text[i + 2] === '\n'))) {
|
|
73
|
+
i += text[i + 1] === '\n' ? 2 : 3;
|
|
74
|
+
result += ' ';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
result += char;
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result.trim();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The offsets the lines of a text start at
|
|
85
|
+
* @param text
|
|
86
|
+
*/
|
|
87
|
+
function lineStartsOf(text) {
|
|
88
|
+
const starts = [0];
|
|
89
|
+
for (let i = 0; i < text.length; i++)
|
|
90
|
+
if (text.charCodeAt(i) === 10)
|
|
91
|
+
starts.push(i + 1);
|
|
92
|
+
return starts;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Where an offset of a text is, for the messages: its line and its column (from 1)
|
|
96
|
+
* @param starts the offsets the lines start at
|
|
97
|
+
* @param offset
|
|
98
|
+
*/
|
|
99
|
+
function positionOf(starts, offset) {
|
|
100
|
+
let low = 0;
|
|
101
|
+
let high = starts.length - 1;
|
|
102
|
+
while (low < high) {
|
|
103
|
+
const middle = (low + high + 1) >> 1;
|
|
104
|
+
if (starts[middle] <= offset)
|
|
105
|
+
low = middle;
|
|
106
|
+
else
|
|
107
|
+
high = middle - 1;
|
|
108
|
+
}
|
|
109
|
+
return { line: low + 1, column: offset - starts[low] + 1 };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The exception of a declaration that cannot be read, at a line and a column
|
|
113
|
+
* @param message
|
|
114
|
+
* @param line
|
|
115
|
+
* @param column
|
|
116
|
+
*/
|
|
117
|
+
export function declarationError(message, line, column) {
|
|
118
|
+
return new NativeDeclarationException(`${message} (line ${line}, column ${column})`);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The file and the line a line marker of a preprocessor gives to the line after it: `# 12 "file.h" 2` (GCC, Clang),
|
|
122
|
+
* `#line 12 "file.h"` (Microsoft); undefined for another directive
|
|
123
|
+
* @param directive the directive, without its #
|
|
124
|
+
*/
|
|
125
|
+
function lineMarker(directive) {
|
|
126
|
+
const marker = directive.match(/^(?:line\s+)?(\d+)(?:\s+"((?:[^"\\]|\\.)*)")?/);
|
|
127
|
+
if (!marker)
|
|
128
|
+
return undefined;
|
|
129
|
+
return { line: Number(marker[1]), ...marker[2] !== undefined ? { file: marker[2].replace(/\\(.)/g, '$1') } : {} };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The tokens of C declarations. Comments are skipped; of the directives of the preprocessor, #pragma pack, #define and
|
|
133
|
+
* #undef are kept (as tokens holding the rest of their line), #include and the other #pragma are skipped, and the
|
|
134
|
+
* conditional ones (#if, #ifdef…) are refused: what they select is the work of a preprocessor. The line markers of a
|
|
135
|
+
* preprocessed text give each token the file and the line it comes from
|
|
136
|
+
* @param text
|
|
137
|
+
*/
|
|
138
|
+
export function tokenize(text) {
|
|
139
|
+
const tokens = [];
|
|
140
|
+
const length = text.length;
|
|
141
|
+
let i = 0;
|
|
142
|
+
let lineStart = true;
|
|
143
|
+
const starts = lineStartsOf(text);
|
|
144
|
+
//The file of the line markers, and what to add to a line of the text to get its line in that file
|
|
145
|
+
let file;
|
|
146
|
+
let lineOffset = 0;
|
|
147
|
+
let marked = false;
|
|
148
|
+
const fail = (message, offset) => {
|
|
149
|
+
const { line, column } = positionOf(starts, offset);
|
|
150
|
+
throw declarationError(message, line, column);
|
|
151
|
+
};
|
|
152
|
+
const push = (kind, start, end, extra = {}) => {
|
|
153
|
+
const { line, column } = positionOf(starts, start);
|
|
154
|
+
tokens.push({
|
|
155
|
+
kind: kind,
|
|
156
|
+
text: extra.text ?? text.slice(start, end),
|
|
157
|
+
value: extra.value,
|
|
158
|
+
float: extra.float,
|
|
159
|
+
line: line,
|
|
160
|
+
column: column,
|
|
161
|
+
...marked ? { file: file, fileLine: line + lineOffset } : {},
|
|
162
|
+
...extra.source !== undefined ? { source: extra.source } : {}
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
while (i < length) {
|
|
166
|
+
const char = text[i];
|
|
167
|
+
//Whitespace, the continuations of lines
|
|
168
|
+
if (char === '\n') {
|
|
169
|
+
lineStart = true;
|
|
170
|
+
i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (char === ' ' || char === '\t' || char === '\r' || char === '\f' || char === '\v') {
|
|
174
|
+
i++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (char === '\\' && (text[i + 1] === '\n' || (text[i + 1] === '\r' && text[i + 2] === '\n'))) {
|
|
178
|
+
i += text[i + 1] === '\n' ? 2 : 3;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
//Comments
|
|
182
|
+
if (char === '/' && text[i + 1] === '/') {
|
|
183
|
+
while (i < length && text[i] !== '\n')
|
|
184
|
+
i++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (char === '/' && text[i + 1] === '*') {
|
|
188
|
+
const end = text.indexOf('*/', i + 2);
|
|
189
|
+
if (end < 0)
|
|
190
|
+
fail('A comment is not closed', i);
|
|
191
|
+
i = end + 2;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
//Directives of the preprocessor, at the start of a line
|
|
195
|
+
if (char === '#' && lineStart) {
|
|
196
|
+
const start = i;
|
|
197
|
+
const end = directiveEnd(text, i);
|
|
198
|
+
const directive = withoutComments(text.slice(start + 1, end));
|
|
199
|
+
const name = directive.match(/^[A-Za-z_]\w*/)?.[0] ?? '';
|
|
200
|
+
if (CONDITIONALS.includes(name))
|
|
201
|
+
fail(`The directive #${name} is not supported: run the header through the preprocessor first (cc -E, cl /P)`, start);
|
|
202
|
+
const marker = name === '' || name === 'line' ? lineMarker(directive) : undefined;
|
|
203
|
+
if (marker) {
|
|
204
|
+
marked = true;
|
|
205
|
+
if (marker.file !== undefined)
|
|
206
|
+
file = marker.file;
|
|
207
|
+
lineOffset = marker.line - (positionOf(starts, start).line + 1);
|
|
208
|
+
}
|
|
209
|
+
const pack = directive.match(/^pragma\s+pack\s*\(([^)]*)\)/);
|
|
210
|
+
if (pack)
|
|
211
|
+
push('pragma', start, end, { text: `pack(${pack[1].replace(/\s+/g, '')})` });
|
|
212
|
+
//The text of a macro after its name, its comments removed; the name directly followed by ( takes arguments
|
|
213
|
+
if (name === 'define' || name === 'undef')
|
|
214
|
+
push(name, start, end, { text: directive.slice(name.length).trim() });
|
|
215
|
+
i = end;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
lineStart = false;
|
|
219
|
+
//Identifiers, and the prefixes of the wide strings and characters
|
|
220
|
+
if (/[A-Za-z_$]/.test(char)) {
|
|
221
|
+
let end = i + 1;
|
|
222
|
+
while (end < length && /[\w$]/.test(text[end]))
|
|
223
|
+
end++;
|
|
224
|
+
const word = text.slice(i, end);
|
|
225
|
+
if ((text[end] === '\'' || text[end] === '"') && ['L', 'u', 'U', 'u8'].includes(word)) {
|
|
226
|
+
i = end;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
push('identifier', i, end);
|
|
230
|
+
i = end;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
//Numbers
|
|
234
|
+
if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(text[i + 1] ?? ''))) {
|
|
235
|
+
let end = i;
|
|
236
|
+
const hex = /^0[xX]/.test(text.slice(i, i + 2));
|
|
237
|
+
if (hex)
|
|
238
|
+
end += 2;
|
|
239
|
+
while (end < length && (/[\w.]/.test(text[end]) || (/[eEpP]/.test(text[end - 1]) && /[+-]/.test(text[end]) && (!hex || /[pP]/.test(text[end - 1])))))
|
|
240
|
+
end++;
|
|
241
|
+
const literal = text.slice(i, end);
|
|
242
|
+
//The suffixes of C, and the ones of Microsoft (i64, ui64, I32…)
|
|
243
|
+
const body = literal.replace(/(?:[uU](?:ll|LL|l|L)?|(?:ll|LL|l|L)[uU]?|[uU]?[iI](?:8|16|32|64))$/, '');
|
|
244
|
+
const float = hex ? /[.pP]/.test(body) : /[.eE]/.test(body) || /[fF]$/.test(literal);
|
|
245
|
+
if (float) {
|
|
246
|
+
push('number', i, end, { float: true });
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
let value;
|
|
250
|
+
try {
|
|
251
|
+
if (/^0[bB]/.test(body))
|
|
252
|
+
value = BigInt(body);
|
|
253
|
+
else if (/^0[0-7]+$/.test(body))
|
|
254
|
+
value = BigInt(`0o${body.slice(1)}`);
|
|
255
|
+
else
|
|
256
|
+
value = BigInt(body);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return fail(`The number ${literal} is not valid`, i);
|
|
260
|
+
}
|
|
261
|
+
push('number', i, end, { value: value });
|
|
262
|
+
}
|
|
263
|
+
i = end;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
//Characters and strings
|
|
267
|
+
if (char === '\'' || char === '"') {
|
|
268
|
+
let end = i + 1;
|
|
269
|
+
const codes = [];
|
|
270
|
+
while (end < length && text[end] !== char) {
|
|
271
|
+
if (text[end] === '\n')
|
|
272
|
+
fail(char === '"' ? 'A string is not closed' : 'A character is not closed', i);
|
|
273
|
+
if (text[end] === '\\') {
|
|
274
|
+
const next = text[end + 1];
|
|
275
|
+
if (next === 'x') {
|
|
276
|
+
const digits = text.slice(end + 2).match(/^[0-9a-fA-F]+/)?.[0] ?? '';
|
|
277
|
+
codes.push(parseInt(digits || '0', 16));
|
|
278
|
+
end += 2 + digits.length;
|
|
279
|
+
}
|
|
280
|
+
else if (/[0-7]/.test(next)) {
|
|
281
|
+
const digits = text.slice(end + 1).match(/^[0-7]{1,3}/)[0];
|
|
282
|
+
codes.push(parseInt(digits, 8));
|
|
283
|
+
end += 1 + digits.length;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
codes.push(ESCAPES[next] ?? next.charCodeAt(0));
|
|
287
|
+
end += 2;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
codes.push(text.codePointAt(end));
|
|
292
|
+
end += text.codePointAt(end) > 0xFFFF ? 2 : 1;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (end >= length)
|
|
296
|
+
fail(char === '"' ? 'A string is not closed' : 'A character is not closed', i);
|
|
297
|
+
if (char === '\'')
|
|
298
|
+
push('char', i, end + 1, { value: BigInt(codes[0] ?? 0) });
|
|
299
|
+
else
|
|
300
|
+
push('string', i, end + 1, { text: String.fromCodePoint(...codes), source: text.slice(i, end + 1) });
|
|
301
|
+
i = end + 1;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
//Punctuators
|
|
305
|
+
const punctuator = PUNCTUATORS.find((candidate) => text.startsWith(candidate, i));
|
|
306
|
+
if (punctuator) {
|
|
307
|
+
push('punctuator', i, i + punctuator.length);
|
|
308
|
+
i += punctuator.length;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (SINGLE.includes(char)) {
|
|
312
|
+
push('punctuator', i, i + 1);
|
|
313
|
+
i++;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
fail(`Unexpected character ${JSON.stringify(char)}`, i);
|
|
317
|
+
}
|
|
318
|
+
const { line, column } = positionOf(starts, length);
|
|
319
|
+
tokens.push({ kind: 'end', text: '', line: line, column: column });
|
|
320
|
+
return tokens;
|
|
321
|
+
}
|