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