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.
@@ -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
+ }