gofront 0.0.1
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 +9 -0
- package/README.md +202 -0
- package/package.json +41 -0
- package/src/codegen.js +1192 -0
- package/src/compiler.js +156 -0
- package/src/dts-parser.js +762 -0
- package/src/index.js +338 -0
- package/src/lexer.js +465 -0
- package/src/parser.js +1120 -0
- package/src/resolver.js +114 -0
- package/src/typechecker.js +1252 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
// Parses TypeScript .d.ts declaration files into GoFront's type system.
|
|
2
|
+
//
|
|
3
|
+
// Produces:
|
|
4
|
+
// { types: Map<name, GoFrontType>, values: Map<name, GoFrontType> }
|
|
5
|
+
//
|
|
6
|
+
// Types / values are kept separate because TypeScript allows a name to be
|
|
7
|
+
// both a type alias (e.g. `export type mat4 = ...`) and a namespace
|
|
8
|
+
// (e.g. `export namespace mat4 { function create(): mat4 }`).
|
|
9
|
+
//
|
|
10
|
+
// Complex TypeScript types (generics, unions, intersections, mapped types,
|
|
11
|
+
// conditional types, etc.) are simplified to `any`.
|
|
12
|
+
|
|
13
|
+
const ANY = { kind: "basic", name: "any" };
|
|
14
|
+
const VOID = { kind: "basic", name: "void" };
|
|
15
|
+
|
|
16
|
+
const TS_PRIMITIVES = {
|
|
17
|
+
number: { kind: "basic", name: "float64" },
|
|
18
|
+
string: { kind: "basic", name: "string" },
|
|
19
|
+
boolean: { kind: "basic", name: "bool" },
|
|
20
|
+
bool: { kind: "basic", name: "bool" },
|
|
21
|
+
void: VOID,
|
|
22
|
+
any: ANY,
|
|
23
|
+
never: ANY,
|
|
24
|
+
unknown: ANY,
|
|
25
|
+
object: ANY,
|
|
26
|
+
null: { kind: "basic", name: "nil" },
|
|
27
|
+
undefined: { kind: "basic", name: "nil" },
|
|
28
|
+
symbol: ANY,
|
|
29
|
+
bigint: ANY,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const SKIP_MODIFIERS = new Set([
|
|
33
|
+
"export",
|
|
34
|
+
"declare",
|
|
35
|
+
"abstract",
|
|
36
|
+
"readonly",
|
|
37
|
+
"static",
|
|
38
|
+
"public",
|
|
39
|
+
"private",
|
|
40
|
+
"protected",
|
|
41
|
+
"override",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
export class DtsParser {
|
|
45
|
+
constructor(source) {
|
|
46
|
+
this.src = source;
|
|
47
|
+
this.pos = 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Low-level helpers ────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
eof() {
|
|
53
|
+
return this.pos >= this.src.length;
|
|
54
|
+
}
|
|
55
|
+
ch() {
|
|
56
|
+
return this.src[this.pos];
|
|
57
|
+
}
|
|
58
|
+
ch1() {
|
|
59
|
+
return this.src[this.pos + 1];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
skip() {
|
|
63
|
+
while (!this.eof()) {
|
|
64
|
+
const c = this.ch();
|
|
65
|
+
if (c === " " || c === "\t" || c === "\r" || c === "\n") {
|
|
66
|
+
this.pos++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (c === "/" && this.ch1() === "/") {
|
|
70
|
+
while (!this.eof() && this.ch() !== "\n") this.pos++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (c === "/" && this.ch1() === "*") {
|
|
74
|
+
this.pos += 2;
|
|
75
|
+
while (!this.eof() && !(this.ch() === "*" && this.ch1() === "/"))
|
|
76
|
+
this.pos++;
|
|
77
|
+
this.pos += 2;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
peek() {
|
|
85
|
+
this.skip();
|
|
86
|
+
return this.eof() ? "" : this.ch();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
startsWith(s) {
|
|
90
|
+
return this.src.startsWith(s, this.pos);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
matchKw(kw) {
|
|
94
|
+
this.skip();
|
|
95
|
+
if (!this.src.startsWith(kw, this.pos)) return false;
|
|
96
|
+
const next = this.src[this.pos + kw.length];
|
|
97
|
+
if (next && /[a-zA-Z0-9_$]/.test(next)) return false; // not a whole word
|
|
98
|
+
this.pos += kw.length;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
readIdent() {
|
|
103
|
+
this.skip();
|
|
104
|
+
let s = "";
|
|
105
|
+
while (!this.eof() && /[a-zA-Z0-9_$]/.test(this.ch()))
|
|
106
|
+
s += this.src[this.pos++];
|
|
107
|
+
return s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
consume(ch) {
|
|
111
|
+
this.skip();
|
|
112
|
+
if (this.ch() === ch) {
|
|
113
|
+
this.pos++;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Skip a string literal
|
|
120
|
+
skipStringLit() {
|
|
121
|
+
const q = this.ch();
|
|
122
|
+
this.pos++;
|
|
123
|
+
while (!this.eof() && this.ch() !== q) {
|
|
124
|
+
if (this.ch() === "\\") this.pos++;
|
|
125
|
+
this.pos++;
|
|
126
|
+
}
|
|
127
|
+
this.pos++; // closing quote
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Skip balanced <...> (generic params)
|
|
131
|
+
skipGenerics() {
|
|
132
|
+
if (this.peek() !== "<") return;
|
|
133
|
+
let d = 1;
|
|
134
|
+
this.pos++;
|
|
135
|
+
while (!this.eof() && d > 0) {
|
|
136
|
+
const c = this.ch();
|
|
137
|
+
if (c === "<") d++;
|
|
138
|
+
else if (c === ">") d--;
|
|
139
|
+
else if (c === '"' || c === "'") {
|
|
140
|
+
this.skipStringLit();
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
this.pos++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Skip balanced {...}
|
|
148
|
+
skipBlock() {
|
|
149
|
+
this.skip();
|
|
150
|
+
if (this.ch() !== "{") return;
|
|
151
|
+
let d = 1;
|
|
152
|
+
this.pos++;
|
|
153
|
+
while (!this.eof() && d > 0) {
|
|
154
|
+
const c = this.ch();
|
|
155
|
+
if (c === "{") d++;
|
|
156
|
+
else if (c === "}") d--;
|
|
157
|
+
else if (c === '"' || c === "'") {
|
|
158
|
+
this.skipStringLit();
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
this.pos++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Skip to the end of a type expression (stops at ; | , | ) | } | { | newline-after-token)
|
|
166
|
+
skipTypeExpr(stopAt = ";,)}{") {
|
|
167
|
+
let angleBrackets = 0,
|
|
168
|
+
parens = 0,
|
|
169
|
+
brackets = 0,
|
|
170
|
+
curlies = 0;
|
|
171
|
+
while (!this.eof()) {
|
|
172
|
+
const c = this.ch();
|
|
173
|
+
if (c === "<") {
|
|
174
|
+
angleBrackets++;
|
|
175
|
+
this.pos++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (c === ">" && angleBrackets > 0) {
|
|
179
|
+
angleBrackets--;
|
|
180
|
+
this.pos++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (c === "(") {
|
|
184
|
+
parens++;
|
|
185
|
+
this.pos++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (c === ")" && parens > 0) {
|
|
189
|
+
parens--;
|
|
190
|
+
this.pos++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (c === "[") {
|
|
194
|
+
brackets++;
|
|
195
|
+
this.pos++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (c === "]" && brackets > 0) {
|
|
199
|
+
brackets--;
|
|
200
|
+
this.pos++;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (c === "{") {
|
|
204
|
+
curlies++;
|
|
205
|
+
this.pos++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (c === "}" && curlies > 0) {
|
|
209
|
+
curlies--;
|
|
210
|
+
this.pos++;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (
|
|
214
|
+
angleBrackets === 0 &&
|
|
215
|
+
parens === 0 &&
|
|
216
|
+
brackets === 0 &&
|
|
217
|
+
curlies === 0 &&
|
|
218
|
+
stopAt.includes(c)
|
|
219
|
+
)
|
|
220
|
+
break;
|
|
221
|
+
if (c === '"' || c === "'") {
|
|
222
|
+
this.skipStringLit();
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
this.pos++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Type parsing ─────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
// Parse a TypeScript type expression and return a GoFront type.
|
|
232
|
+
// Always returns something; falls back to ANY for complex types.
|
|
233
|
+
parseType() {
|
|
234
|
+
this.skip();
|
|
235
|
+
|
|
236
|
+
// Skip modifiers
|
|
237
|
+
while (this.matchKw("readonly") || this.matchKw("unique")) this.skip();
|
|
238
|
+
|
|
239
|
+
// typeof X
|
|
240
|
+
if (this.matchKw("typeof")) {
|
|
241
|
+
this.skipTypeExpr();
|
|
242
|
+
return ANY;
|
|
243
|
+
}
|
|
244
|
+
// keyof T
|
|
245
|
+
if (this.matchKw("keyof")) {
|
|
246
|
+
this.parseType();
|
|
247
|
+
return ANY;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Skip leading | or & (e.g. `export type X =\n | T1\n | T2`)
|
|
251
|
+
this.skip();
|
|
252
|
+
if (this.ch() === "|" || this.ch() === "&") this.pos++;
|
|
253
|
+
|
|
254
|
+
let base = this._parseBaseType();
|
|
255
|
+
|
|
256
|
+
this.skip();
|
|
257
|
+
// Array suffix: T[] or T[][]
|
|
258
|
+
while (this.startsWith("[]")) {
|
|
259
|
+
this.pos += 2;
|
|
260
|
+
base = { kind: "slice", elem: base };
|
|
261
|
+
this.skip();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Union / intersection: T | U or T & U → simplify to first concrete type
|
|
265
|
+
// (skip() handles multi-line unions where | is on the next line)
|
|
266
|
+
this.skip();
|
|
267
|
+
while (!this.eof() && (this.ch() === "|" || this.ch() === "&")) {
|
|
268
|
+
this.pos++;
|
|
269
|
+
this.parseType(); // discard (already calls skip() at start)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return base;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_parseBaseType() {
|
|
276
|
+
this.skip();
|
|
277
|
+
const c = this.ch();
|
|
278
|
+
|
|
279
|
+
// Tuple / array literal type: [T, U]
|
|
280
|
+
if (c === "[") {
|
|
281
|
+
this.pos++; // consume opening [, so skipTypeExpr stops at depth-0 ]
|
|
282
|
+
this.skipTypeExpr("]");
|
|
283
|
+
this.consume("]");
|
|
284
|
+
return ANY;
|
|
285
|
+
}
|
|
286
|
+
// Object literal type: { x: T }
|
|
287
|
+
if (c === "{") {
|
|
288
|
+
this.skipBlock();
|
|
289
|
+
return ANY;
|
|
290
|
+
}
|
|
291
|
+
// Parenthesised type or function type: (params) => T
|
|
292
|
+
if (c === "(") {
|
|
293
|
+
this.pos++; // consume opening (, so skipTypeExpr stops at depth-0 )
|
|
294
|
+
this.skipTypeExpr(")");
|
|
295
|
+
this.consume(")");
|
|
296
|
+
this.skip();
|
|
297
|
+
if (this.startsWith("=>")) {
|
|
298
|
+
this.pos += 2;
|
|
299
|
+
this.parseType();
|
|
300
|
+
}
|
|
301
|
+
return ANY;
|
|
302
|
+
}
|
|
303
|
+
// String literal type: "foo"
|
|
304
|
+
if (c === '"' || c === "'") {
|
|
305
|
+
this.skipStringLit();
|
|
306
|
+
return { kind: "basic", name: "string" };
|
|
307
|
+
}
|
|
308
|
+
// Template literal: `...`
|
|
309
|
+
if (c === "`") {
|
|
310
|
+
while (!this.eof() && this.ch() !== "`") this.pos++;
|
|
311
|
+
this.pos++;
|
|
312
|
+
return { kind: "basic", name: "string" };
|
|
313
|
+
}
|
|
314
|
+
// Numeric literal
|
|
315
|
+
if (
|
|
316
|
+
/[0-9]/.test(c) ||
|
|
317
|
+
(c === "-" && /[0-9]/.test(this.src[this.pos + 1]))
|
|
318
|
+
) {
|
|
319
|
+
while (!this.eof() && /[0-9._-]/.test(this.ch())) this.pos++;
|
|
320
|
+
return { kind: "basic", name: "float64" };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Named type (including primitives)
|
|
324
|
+
let name = "";
|
|
325
|
+
while (!this.eof() && /[a-zA-Z0-9_$]/.test(this.ch()))
|
|
326
|
+
name += this.src[this.pos++];
|
|
327
|
+
if (!name) {
|
|
328
|
+
this.pos++;
|
|
329
|
+
return ANY;
|
|
330
|
+
} // skip unknown char
|
|
331
|
+
|
|
332
|
+
if (TS_PRIMITIVES[name]) {
|
|
333
|
+
this.skipGenerics();
|
|
334
|
+
return TS_PRIMITIVES[name];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// true / false
|
|
338
|
+
if (name === "true" || name === "false")
|
|
339
|
+
return { kind: "basic", name: "bool" };
|
|
340
|
+
|
|
341
|
+
// Generic application: T<U> — skip the parameters
|
|
342
|
+
this.skipGenerics();
|
|
343
|
+
|
|
344
|
+
// Qualified name: A.B
|
|
345
|
+
while (this.ch() === ".") {
|
|
346
|
+
this.pos++;
|
|
347
|
+
while (!this.eof() && /[a-zA-Z0-9_$]/.test(this.ch())) this.pos++;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Return as a named any-alias (the type checker will resolve or fall back to any)
|
|
351
|
+
return { kind: "basic", name: "any", alias: name };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── Parameter list ───────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
parseParams() {
|
|
357
|
+
this.consume("(");
|
|
358
|
+
const params = [];
|
|
359
|
+
while (!this.eof() && this.peek() !== ")") {
|
|
360
|
+
this.skip();
|
|
361
|
+
if (this.ch() === ")") break;
|
|
362
|
+
|
|
363
|
+
// rest param: ...name
|
|
364
|
+
const isRest = this.startsWith("...");
|
|
365
|
+
if (isRest) this.pos += 3;
|
|
366
|
+
|
|
367
|
+
// param name (may be followed by ? for optional)
|
|
368
|
+
const name = this.readIdent() || "_";
|
|
369
|
+
this.skip();
|
|
370
|
+
this.consume("?"); // optional marker
|
|
371
|
+
|
|
372
|
+
// Type annotation
|
|
373
|
+
let type = ANY;
|
|
374
|
+
this.skip();
|
|
375
|
+
if (this.ch() === ":") {
|
|
376
|
+
this.pos++;
|
|
377
|
+
type = this.parseType();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
params.push({ name, type, variadic: isRest });
|
|
381
|
+
|
|
382
|
+
this.skip();
|
|
383
|
+
if (this.ch() !== ",") break;
|
|
384
|
+
this.pos++;
|
|
385
|
+
}
|
|
386
|
+
this.consume(")");
|
|
387
|
+
return params;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── Body parsing ─────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
// Parse the interior of a namespace/class/interface/module block.
|
|
393
|
+
// Returns a plain object: { memberName: GoFrontType }
|
|
394
|
+
parseBody() {
|
|
395
|
+
const members = {};
|
|
396
|
+
while (!this.eof() && this.peek() !== "}") {
|
|
397
|
+
this._parseMember(members);
|
|
398
|
+
}
|
|
399
|
+
return members;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
_parseMember(out) {
|
|
403
|
+
this.skip();
|
|
404
|
+
if (this.eof() || this.ch() === "}") return;
|
|
405
|
+
|
|
406
|
+
// Skip modifiers (export, declare, static, readonly, etc.)
|
|
407
|
+
let _exported = false;
|
|
408
|
+
while (true) {
|
|
409
|
+
this.skip();
|
|
410
|
+
let found = false;
|
|
411
|
+
for (const mod of SKIP_MODIFIERS) {
|
|
412
|
+
if (this.matchKw(mod)) {
|
|
413
|
+
if (mod === "export") _exported = true;
|
|
414
|
+
found = true;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!found) break;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.skip();
|
|
422
|
+
if (this.eof() || this.ch() === "}") return;
|
|
423
|
+
|
|
424
|
+
// Read the first identifier (keyword or name)
|
|
425
|
+
const kw = this.readIdent();
|
|
426
|
+
if (!kw) {
|
|
427
|
+
this.pos++;
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this.skip();
|
|
432
|
+
switch (kw) {
|
|
433
|
+
case "function": {
|
|
434
|
+
const name = this.readIdent();
|
|
435
|
+
this.skipGenerics();
|
|
436
|
+
const params = this.parseParams();
|
|
437
|
+
this.skip();
|
|
438
|
+
let ret = VOID;
|
|
439
|
+
if (this.ch() === ":") {
|
|
440
|
+
this.pos++;
|
|
441
|
+
ret = this.parseType();
|
|
442
|
+
}
|
|
443
|
+
this.consume(";");
|
|
444
|
+
if (name)
|
|
445
|
+
out[name] = {
|
|
446
|
+
kind: "func",
|
|
447
|
+
params: params.map((p) => p.type),
|
|
448
|
+
returns: [ret],
|
|
449
|
+
};
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case "new": {
|
|
453
|
+
// constructor signature in interface
|
|
454
|
+
this.skipGenerics();
|
|
455
|
+
this.parseParams();
|
|
456
|
+
this.skip();
|
|
457
|
+
if (this.ch() === ":") {
|
|
458
|
+
this.pos++;
|
|
459
|
+
this.parseType();
|
|
460
|
+
}
|
|
461
|
+
this.consume(";");
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case "type": {
|
|
465
|
+
const name = this.readIdent();
|
|
466
|
+
this.skipGenerics();
|
|
467
|
+
this.skip();
|
|
468
|
+
let type = ANY;
|
|
469
|
+
if (this.ch() === "=") {
|
|
470
|
+
this.pos++;
|
|
471
|
+
type = this.parseType();
|
|
472
|
+
}
|
|
473
|
+
this.consume(";");
|
|
474
|
+
if (name) out[name] = type;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
case "interface":
|
|
478
|
+
case "class": {
|
|
479
|
+
const name = this.readIdent();
|
|
480
|
+
this.skipGenerics();
|
|
481
|
+
// skip extends/implements
|
|
482
|
+
while (!this.eof() && this.ch() !== "{") this.pos++;
|
|
483
|
+
this.consume("{");
|
|
484
|
+
const body = this.parseBody();
|
|
485
|
+
this.consume("}");
|
|
486
|
+
this.consume(";");
|
|
487
|
+
if (name) out[name] = { kind: "namespace", name, members: body };
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case "namespace":
|
|
491
|
+
case "module": {
|
|
492
|
+
const name = this.readIdent() || this._readStringLit();
|
|
493
|
+
this.skip();
|
|
494
|
+
this.consume("{");
|
|
495
|
+
const body = this.parseBody();
|
|
496
|
+
this.consume("}");
|
|
497
|
+
this.consume(";");
|
|
498
|
+
if (name) out[name] = { kind: "namespace", name, members: body };
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case "enum": {
|
|
502
|
+
const name = this.readIdent();
|
|
503
|
+
this.skip();
|
|
504
|
+
this.consume("{");
|
|
505
|
+
// skip enum body — members are string/number constants
|
|
506
|
+
while (!this.eof() && this.ch() !== "}") {
|
|
507
|
+
this.readIdent();
|
|
508
|
+
this.skip();
|
|
509
|
+
if (this.ch() === "=") {
|
|
510
|
+
this.pos++;
|
|
511
|
+
this.skipTypeExpr(",}");
|
|
512
|
+
}
|
|
513
|
+
this.consume(",");
|
|
514
|
+
}
|
|
515
|
+
this.consume("}");
|
|
516
|
+
if (name) out[name] = ANY;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
case "const":
|
|
520
|
+
case "let":
|
|
521
|
+
case "var": {
|
|
522
|
+
const name = this.readIdent();
|
|
523
|
+
this.skip();
|
|
524
|
+
this.consume("?");
|
|
525
|
+
let type = ANY;
|
|
526
|
+
if (this.ch() === ":") {
|
|
527
|
+
this.pos++;
|
|
528
|
+
type = this.parseType();
|
|
529
|
+
}
|
|
530
|
+
this.skip();
|
|
531
|
+
if (this.ch() === "=") {
|
|
532
|
+
this.pos++;
|
|
533
|
+
this.skipTypeExpr();
|
|
534
|
+
}
|
|
535
|
+
this.consume(";");
|
|
536
|
+
if (name) out[name] = type;
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
default: {
|
|
540
|
+
// Could be: propertyName: Type or methodName(params): Type
|
|
541
|
+
const name = kw;
|
|
542
|
+
this.skip();
|
|
543
|
+
this.consume("?"); // optional property
|
|
544
|
+
this.skipGenerics();
|
|
545
|
+
this.skip();
|
|
546
|
+
|
|
547
|
+
if (this.ch() === "(") {
|
|
548
|
+
// Method signature
|
|
549
|
+
const params = this.parseParams();
|
|
550
|
+
this.skip();
|
|
551
|
+
let ret = VOID;
|
|
552
|
+
if (this.ch() === ":") {
|
|
553
|
+
this.pos++;
|
|
554
|
+
ret = this.parseType();
|
|
555
|
+
}
|
|
556
|
+
this.consume(";");
|
|
557
|
+
out[name] = {
|
|
558
|
+
kind: "func",
|
|
559
|
+
params: params.map((p) => p.type),
|
|
560
|
+
returns: [ret],
|
|
561
|
+
};
|
|
562
|
+
} else if (this.ch() === ":") {
|
|
563
|
+
this.pos++;
|
|
564
|
+
const type = this.parseType();
|
|
565
|
+
this.consume(";");
|
|
566
|
+
out[name] = type;
|
|
567
|
+
} else {
|
|
568
|
+
// Unknown — skip to next ;
|
|
569
|
+
this.skipTypeExpr();
|
|
570
|
+
this.consume(";");
|
|
571
|
+
}
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
_readStringLit() {
|
|
578
|
+
this.skip();
|
|
579
|
+
const c = this.ch();
|
|
580
|
+
if (c !== '"' && c !== "'") return null;
|
|
581
|
+
this.pos++;
|
|
582
|
+
let s = "";
|
|
583
|
+
while (!this.eof() && this.ch() !== c) s += this.src[this.pos++];
|
|
584
|
+
this.pos++;
|
|
585
|
+
return s;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ── Top-level parse ──────────────────────────────────────────
|
|
589
|
+
|
|
590
|
+
parse() {
|
|
591
|
+
const types = new Map(); // type namespace (for type annotations)
|
|
592
|
+
const values = new Map(); // value namespace (for expressions)
|
|
593
|
+
|
|
594
|
+
// If wrapped in `declare module "name" { ... }`, strip the wrapper
|
|
595
|
+
this.skip();
|
|
596
|
+
if (this.matchKw("declare") && this.matchKw("module")) {
|
|
597
|
+
this._readStringLit() || this.readIdent();
|
|
598
|
+
this.skip();
|
|
599
|
+
this.consume("{");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
while (!this.eof() && this.peek() !== "}") {
|
|
603
|
+
this.skip();
|
|
604
|
+
if (this.eof() || this.ch() === "}") break;
|
|
605
|
+
|
|
606
|
+
// Collect modifiers
|
|
607
|
+
let _isExport = false;
|
|
608
|
+
while (true) {
|
|
609
|
+
this.skip();
|
|
610
|
+
let found = false;
|
|
611
|
+
for (const mod of SKIP_MODIFIERS) {
|
|
612
|
+
if (this.matchKw(mod)) {
|
|
613
|
+
if (mod === "export") _isExport = true;
|
|
614
|
+
found = true;
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (!found) break;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
this.skip();
|
|
622
|
+
if (this.eof() || this.ch() === "}") break;
|
|
623
|
+
|
|
624
|
+
const kw = this.readIdent();
|
|
625
|
+
if (!kw) {
|
|
626
|
+
this.pos++;
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this.skip();
|
|
631
|
+
switch (kw) {
|
|
632
|
+
case "import":
|
|
633
|
+
case "export":
|
|
634
|
+
// skip import { ... } from "..." or export { ... }
|
|
635
|
+
this.skipTypeExpr();
|
|
636
|
+
this.consume(";");
|
|
637
|
+
break;
|
|
638
|
+
case "type": {
|
|
639
|
+
const name = this.readIdent();
|
|
640
|
+
this.skipGenerics();
|
|
641
|
+
this.skip();
|
|
642
|
+
let type = ANY;
|
|
643
|
+
if (this.ch() === "=") {
|
|
644
|
+
this.pos++;
|
|
645
|
+
type = this.parseType();
|
|
646
|
+
}
|
|
647
|
+
this.consume(";");
|
|
648
|
+
if (name) types.set(name, type);
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
case "interface": {
|
|
652
|
+
const name = this.readIdent();
|
|
653
|
+
this.skipGenerics();
|
|
654
|
+
while (!this.eof() && this.ch() !== "{") this.pos++;
|
|
655
|
+
this.consume("{");
|
|
656
|
+
const body = this.parseBody();
|
|
657
|
+
this.consume("}");
|
|
658
|
+
this.consume(";");
|
|
659
|
+
if (name)
|
|
660
|
+
types.set(name, {
|
|
661
|
+
kind: "namespace",
|
|
662
|
+
name,
|
|
663
|
+
members: body,
|
|
664
|
+
});
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "namespace":
|
|
668
|
+
case "module": {
|
|
669
|
+
const name = this.readIdent() || this._readStringLit();
|
|
670
|
+
this.skip();
|
|
671
|
+
this.consume("{");
|
|
672
|
+
const body = this.parseBody();
|
|
673
|
+
this.consume("}");
|
|
674
|
+
this.consume(";");
|
|
675
|
+
if (name) {
|
|
676
|
+
const ns = { kind: "namespace", name, members: body };
|
|
677
|
+
types.set(name, ns);
|
|
678
|
+
values.set(name, ns);
|
|
679
|
+
}
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
case "class": {
|
|
683
|
+
const name = this.readIdent();
|
|
684
|
+
this.skipGenerics();
|
|
685
|
+
while (!this.eof() && this.ch() !== "{") this.pos++;
|
|
686
|
+
this.consume("{");
|
|
687
|
+
const body = this.parseBody();
|
|
688
|
+
this.consume("}");
|
|
689
|
+
this.consume(";");
|
|
690
|
+
if (name) {
|
|
691
|
+
const ns = { kind: "namespace", name, members: body };
|
|
692
|
+
types.set(name, ns);
|
|
693
|
+
values.set(name, ns);
|
|
694
|
+
}
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case "enum": {
|
|
698
|
+
const name = this.readIdent();
|
|
699
|
+
this.skipBlock();
|
|
700
|
+
this.consume(";");
|
|
701
|
+
if (name) {
|
|
702
|
+
types.set(name, ANY);
|
|
703
|
+
values.set(name, ANY);
|
|
704
|
+
}
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
case "function": {
|
|
708
|
+
const name = this.readIdent();
|
|
709
|
+
this.skipGenerics();
|
|
710
|
+
const params = this.parseParams();
|
|
711
|
+
this.skip();
|
|
712
|
+
let ret = VOID;
|
|
713
|
+
if (this.ch() === ":") {
|
|
714
|
+
this.pos++;
|
|
715
|
+
ret = this.parseType();
|
|
716
|
+
}
|
|
717
|
+
this.consume(";");
|
|
718
|
+
if (name)
|
|
719
|
+
values.set(name, {
|
|
720
|
+
kind: "func",
|
|
721
|
+
params: params.map((p) => p.type),
|
|
722
|
+
returns: [ret],
|
|
723
|
+
});
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
case "const":
|
|
727
|
+
case "let":
|
|
728
|
+
case "var": {
|
|
729
|
+
const name = this.readIdent();
|
|
730
|
+
this.skip();
|
|
731
|
+
this.consume("?");
|
|
732
|
+
let type = ANY;
|
|
733
|
+
if (this.ch() === ":") {
|
|
734
|
+
this.pos++;
|
|
735
|
+
type = this.parseType();
|
|
736
|
+
}
|
|
737
|
+
this.skip();
|
|
738
|
+
if (this.ch() === "=") {
|
|
739
|
+
this.pos++;
|
|
740
|
+
this.skipTypeExpr();
|
|
741
|
+
}
|
|
742
|
+
this.consume(";");
|
|
743
|
+
if (name) values.set(name, type);
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
default:
|
|
747
|
+
// Unknown top-level construct — skip to next ;
|
|
748
|
+
this.skipTypeExpr();
|
|
749
|
+
this.consume(";");
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return { types, values };
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Helper: parse a .d.ts file and return { types, values } Maps
|
|
759
|
+
export function parseDts(source) {
|
|
760
|
+
const parser = new DtsParser(source);
|
|
761
|
+
return parser.parse();
|
|
762
|
+
}
|