tova 0.3.0 → 0.3.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/bin/tova.js +1401 -111
- package/package.json +3 -1
- package/src/analyzer/analyzer.js +831 -709
- package/src/analyzer/client-analyzer.js +191 -0
- package/src/analyzer/server-analyzer.js +467 -0
- package/src/analyzer/types.js +20 -4
- package/src/codegen/base-codegen.js +467 -109
- package/src/codegen/client-codegen.js +92 -42
- package/src/codegen/codegen.js +65 -5
- package/src/codegen/server-codegen.js +290 -36
- package/src/diagnostics/error-codes.js +255 -0
- package/src/diagnostics/formatter.js +150 -28
- package/src/docs/generator.js +390 -0
- package/src/lexer/lexer.js +305 -63
- package/src/lexer/tokens.js +19 -0
- package/src/lsp/server.js +892 -30
- package/src/parser/ast.js +81 -368
- package/src/parser/client-ast.js +138 -0
- package/src/parser/client-parser.js +504 -0
- package/src/parser/parser.js +491 -1064
- package/src/parser/server-ast.js +240 -0
- package/src/parser/server-parser.js +602 -0
- package/src/runtime/array-proto.js +32 -0
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +191 -10
- package/src/stdlib/advanced-collections.js +81 -0
- package/src/stdlib/inline.js +549 -6
- package/src/version.js +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Base code generation utilities shared across all codegen targets
|
|
2
|
-
import { RESULT_OPTION, PROPAGATE, BUILTIN_NAMES } from '../stdlib/inline.js';
|
|
2
|
+
import { RESULT_OPTION, PROPAGATE, BUILTIN_NAMES, STDLIB_DEPS } from '../stdlib/inline.js';
|
|
3
3
|
import { PIPE_TARGET } from '../parser/ast.js';
|
|
4
4
|
|
|
5
5
|
export class BaseCodegen {
|
|
@@ -12,15 +12,35 @@ export class BaseCodegen {
|
|
|
12
12
|
this._usedBuiltins = new Set(); // track which stdlib builtins are actually used
|
|
13
13
|
this._needsResultOption = false; // track if Ok/Err/Some/None are used
|
|
14
14
|
this._variantFields = { 'Ok': ['value'], 'Err': ['error'], 'Some': ['value'] }; // map variant name -> [field names] for pattern destructuring
|
|
15
|
+
this._traitDecls = new Map(); // traitName -> { methods: [...] }
|
|
16
|
+
this._traitImpls = new Map(); // "TraitName:TypeName" -> ImplDeclaration node
|
|
15
17
|
// Source map tracking
|
|
16
|
-
this._sourceMappings = []; // {sourceLine, sourceCol, outputLine, outputCol}
|
|
18
|
+
this._sourceMappings = []; // {sourceLine, sourceCol, outputLine, outputCol, sourceFile?}
|
|
17
19
|
this._outputLineCount = 0;
|
|
20
|
+
this._sourceFile = null; // current source file for multi-file source maps
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
_uid() {
|
|
21
24
|
return this._counter++;
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
// Returns true for AST nodes with no side effects that are safe to evaluate multiple times
|
|
28
|
+
_isSimpleExpression(node) {
|
|
29
|
+
if (!node) return false;
|
|
30
|
+
switch (node.type) {
|
|
31
|
+
case 'Identifier':
|
|
32
|
+
case 'NumberLiteral':
|
|
33
|
+
case 'StringLiteral':
|
|
34
|
+
case 'BooleanLiteral':
|
|
35
|
+
case 'NilLiteral':
|
|
36
|
+
return true;
|
|
37
|
+
case 'MemberExpression':
|
|
38
|
+
return !node.computed && this._isSimpleExpression(node.object);
|
|
39
|
+
default:
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
// Known void/side-effect-only calls that shouldn't be implicitly returned
|
|
25
45
|
_isVoidCall(expr) {
|
|
26
46
|
if (expr.type !== 'CallExpression') return false;
|
|
@@ -58,18 +78,35 @@ export class BaseCodegen {
|
|
|
58
78
|
return ' '.repeat(this.indent);
|
|
59
79
|
}
|
|
60
80
|
|
|
81
|
+
// Set current source file for multi-file source map tracking
|
|
82
|
+
setSourceFile(filename) {
|
|
83
|
+
this._sourceFile = filename;
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
// Source map: record a mapping from source location to output line
|
|
62
87
|
_addMapping(node, outputLine) {
|
|
63
88
|
if (node && node.loc && node.loc.line) {
|
|
64
|
-
|
|
89
|
+
const mapping = {
|
|
65
90
|
sourceLine: node.loc.line - 1, // 0-based
|
|
66
91
|
sourceCol: (node.loc.column || 1) - 1, // 0-based
|
|
67
92
|
outputLine,
|
|
68
93
|
outputCol: this.indent * 2, // approximate column from indent
|
|
69
|
-
}
|
|
94
|
+
};
|
|
95
|
+
if (this._sourceFile) mapping.sourceFile = this._sourceFile;
|
|
96
|
+
this._sourceMappings.push(mapping);
|
|
70
97
|
}
|
|
71
98
|
}
|
|
72
99
|
|
|
100
|
+
// Count newlines in a generated string to update output line tracking
|
|
101
|
+
_countLines(code) {
|
|
102
|
+
if (!code) return 0;
|
|
103
|
+
let count = 0;
|
|
104
|
+
for (let i = 0; i < code.length; i++) {
|
|
105
|
+
if (code.charCodeAt(i) === 10) count++;
|
|
106
|
+
}
|
|
107
|
+
return count;
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
// Get collected source mappings
|
|
74
111
|
getSourceMappings() {
|
|
75
112
|
return this._sourceMappings;
|
|
@@ -79,13 +116,26 @@ export class BaseCodegen {
|
|
|
79
116
|
return this._usedBuiltins;
|
|
80
117
|
}
|
|
81
118
|
|
|
119
|
+
// Track a builtin and its transitive dependencies from the stdlib dependency graph
|
|
120
|
+
_trackBuiltin(name) {
|
|
121
|
+
this._usedBuiltins.add(name);
|
|
122
|
+
const deps = STDLIB_DEPS[name];
|
|
123
|
+
if (deps) {
|
|
124
|
+
for (const dep of deps) {
|
|
125
|
+
this._usedBuiltins.add(dep);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
82
130
|
getContainsHelper() {
|
|
83
|
-
return
|
|
84
|
-
'
|
|
85
|
-
' if (col
|
|
86
|
-
' if (
|
|
87
|
-
' return
|
|
88
|
-
'
|
|
131
|
+
return [
|
|
132
|
+
'function __contains(col, val) {',
|
|
133
|
+
' if (Array.isArray(col) || typeof col === \'string\') return col.includes(val);',
|
|
134
|
+
' if (col instanceof Set || col instanceof Map) return col.has(val);',
|
|
135
|
+
' if (typeof col === \'object\' && col !== null) return val in col;',
|
|
136
|
+
' return false;',
|
|
137
|
+
'}',
|
|
138
|
+
].join('\n');
|
|
89
139
|
}
|
|
90
140
|
|
|
91
141
|
genPropagateExpression(node) {
|
|
@@ -143,19 +193,21 @@ export class BaseCodegen {
|
|
|
143
193
|
case 'IfStatement': result = this.genIfStatement(node); break;
|
|
144
194
|
case 'ForStatement': result = this.genForStatement(node); break;
|
|
145
195
|
case 'WhileStatement': result = this.genWhileStatement(node); break;
|
|
196
|
+
case 'LoopStatement': result = this.genLoopStatement(node); break;
|
|
146
197
|
case 'TryCatchStatement': result = this.genTryCatchStatement(node); break;
|
|
147
198
|
case 'ReturnStatement': result = this.genReturnStatement(node); break;
|
|
148
199
|
case 'ExpressionStatement': result = `${this.i()}${this.genExpression(node.expression)};`; break;
|
|
149
200
|
case 'BlockStatement': result = this.genBlock(node); break;
|
|
150
201
|
case 'CompoundAssignment': result = this.genCompoundAssignment(node); break;
|
|
151
|
-
case 'BreakStatement': result = `${this.i()}break;`; break;
|
|
152
|
-
case 'ContinueStatement': result = `${this.i()}continue;`; break;
|
|
202
|
+
case 'BreakStatement': result = node.label ? `${this.i()}break ${node.label};` : `${this.i()}break;`; break;
|
|
203
|
+
case 'ContinueStatement': result = node.label ? `${this.i()}continue ${node.label};` : `${this.i()}continue;`; break;
|
|
153
204
|
case 'GuardStatement': result = this.genGuardStatement(node); break;
|
|
154
205
|
case 'InterfaceDeclaration': result = this.genInterfaceDeclaration(node); break;
|
|
155
206
|
case 'ImplDeclaration': result = this.genImplDeclaration(node); break;
|
|
156
207
|
case 'TraitDeclaration': result = this.genTraitDeclaration(node); break;
|
|
157
208
|
case 'TypeAlias': result = this.genTypeAlias(node); break;
|
|
158
209
|
case 'DeferStatement': result = this.genDeferStatement(node); break;
|
|
210
|
+
case 'WithStatement': result = this.genWithStatement(node); break;
|
|
159
211
|
case 'ExternDeclaration': result = `${this.i()}// extern: ${node.name}`; break;
|
|
160
212
|
// Config declarations handled at block level — emit nothing in statement context
|
|
161
213
|
case 'AiConfigDeclaration': result = ''; break;
|
|
@@ -169,10 +221,9 @@ export class BaseCodegen {
|
|
|
169
221
|
result = `${this.i()}${this.genExpression(node)};`;
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
// Track output line count
|
|
224
|
+
// Track output line count using fast character scan
|
|
173
225
|
if (result) {
|
|
174
|
-
|
|
175
|
-
this._outputLineCount += newlines + 1; // +1 for the line itself (join with \n)
|
|
226
|
+
this._outputLineCount += this._countLines(result) + 1; // +1 for the join newline
|
|
176
227
|
}
|
|
177
228
|
|
|
178
229
|
return result;
|
|
@@ -202,6 +253,7 @@ export class BaseCodegen {
|
|
|
202
253
|
case 'LogicalExpression': return this.genLogicalExpression(node);
|
|
203
254
|
case 'ChainedComparison': return this.genChainedComparison(node);
|
|
204
255
|
case 'MembershipExpression': return this.genMembershipExpression(node);
|
|
256
|
+
case 'IsExpression': return this.genIsExpression(node);
|
|
205
257
|
case 'CallExpression': return this.genCallExpression(node);
|
|
206
258
|
case 'MemberExpression': return this.genMemberExpression(node);
|
|
207
259
|
case 'OptionalChain': return this.genOptionalChain(node);
|
|
@@ -334,10 +386,20 @@ export class BaseCodegen {
|
|
|
334
386
|
}
|
|
335
387
|
const body = this.genBlockBody(node.body);
|
|
336
388
|
this.popScope();
|
|
389
|
+
const p = [];
|
|
390
|
+
p.push(`${this.i()}${exportPrefix}${asyncPrefix}function${genStar} ${node.name}(${params}) {`);
|
|
337
391
|
if (hasPropagate) {
|
|
338
|
-
|
|
392
|
+
p.push(`${this.i()} try {`);
|
|
393
|
+
p.push(body);
|
|
394
|
+
p.push(`${this.i()} } catch (__e) {`);
|
|
395
|
+
p.push(`${this.i()} if (__e && __e.__tova_propagate) return __e.value;`);
|
|
396
|
+
p.push(`${this.i()} throw __e;`);
|
|
397
|
+
p.push(`${this.i()} }`);
|
|
398
|
+
} else {
|
|
399
|
+
p.push(body);
|
|
339
400
|
}
|
|
340
|
-
|
|
401
|
+
p.push(`${this.i()}}`);
|
|
402
|
+
return p.join('\n');
|
|
341
403
|
}
|
|
342
404
|
|
|
343
405
|
genParams(params) {
|
|
@@ -383,145 +445,171 @@ export class BaseCodegen {
|
|
|
383
445
|
}
|
|
384
446
|
|
|
385
447
|
genIfStatement(node) {
|
|
386
|
-
|
|
448
|
+
const p = [];
|
|
449
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
387
450
|
this.indent++;
|
|
388
451
|
this.pushScope();
|
|
389
|
-
|
|
452
|
+
p.push(this.genBlockStatements(node.consequent));
|
|
390
453
|
this.popScope();
|
|
391
454
|
this.indent--;
|
|
392
|
-
|
|
455
|
+
p.push(`\n${this.i()}}`);
|
|
393
456
|
|
|
394
457
|
for (const alt of node.alternates) {
|
|
395
|
-
|
|
458
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
396
459
|
this.indent++;
|
|
397
460
|
this.pushScope();
|
|
398
|
-
|
|
461
|
+
p.push(this.genBlockStatements(alt.body));
|
|
399
462
|
this.popScope();
|
|
400
463
|
this.indent--;
|
|
401
|
-
|
|
464
|
+
p.push(`\n${this.i()}}`);
|
|
402
465
|
}
|
|
403
466
|
|
|
404
467
|
if (node.elseBody) {
|
|
405
|
-
|
|
468
|
+
p.push(` else {\n`);
|
|
406
469
|
this.indent++;
|
|
407
470
|
this.pushScope();
|
|
408
|
-
|
|
471
|
+
p.push(this.genBlockStatements(node.elseBody));
|
|
409
472
|
this.popScope();
|
|
410
473
|
this.indent--;
|
|
411
|
-
|
|
474
|
+
p.push(`\n${this.i()}}`);
|
|
412
475
|
}
|
|
413
476
|
|
|
414
|
-
return
|
|
477
|
+
return p.join('');
|
|
415
478
|
}
|
|
416
479
|
|
|
417
480
|
genForStatement(node) {
|
|
418
481
|
const vars = Array.isArray(node.variable) ? node.variable : [node.variable];
|
|
419
482
|
const iterExpr = this.genExpression(node.iterable);
|
|
483
|
+
const labelPrefix = node.label ? `${node.label}: ` : '';
|
|
484
|
+
const awaitKeyword = node.isAsync ? ' await' : '';
|
|
420
485
|
|
|
421
486
|
if (node.elseBody) {
|
|
422
487
|
// for-else: run else if iterable was empty
|
|
423
488
|
const tempVar = `__iter_${this._uid()}`;
|
|
424
489
|
const enteredVar = `__entered_${this._uid()}`;
|
|
425
|
-
|
|
490
|
+
const p = [];
|
|
491
|
+
p.push(`${this.i()}{\n`);
|
|
426
492
|
this.indent++;
|
|
427
|
-
|
|
428
|
-
|
|
493
|
+
p.push(`${this.i()}const ${tempVar} = ${iterExpr};\n`);
|
|
494
|
+
p.push(`${this.i()}let ${enteredVar} = false;\n`);
|
|
429
495
|
this.pushScope();
|
|
430
496
|
for (const v of vars) this.declareVar(v);
|
|
431
497
|
if (vars.length === 2) {
|
|
432
|
-
|
|
498
|
+
p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const [${vars[0]}, ${vars[1]}] of ${tempVar}) {\n`);
|
|
433
499
|
} else {
|
|
434
|
-
|
|
500
|
+
p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const ${vars[0]} of ${tempVar}) {\n`);
|
|
435
501
|
}
|
|
436
502
|
this.indent++;
|
|
437
|
-
|
|
438
|
-
|
|
503
|
+
p.push(`${this.i()}${enteredVar} = true;\n`);
|
|
504
|
+
if (node.guard) {
|
|
505
|
+
p.push(`${this.i()}if (!(${this.genExpression(node.guard)})) continue;\n`);
|
|
506
|
+
}
|
|
507
|
+
p.push(this.genBlockStatements(node.body));
|
|
439
508
|
this.indent--;
|
|
440
|
-
|
|
509
|
+
p.push(`\n${this.i()}}\n`);
|
|
441
510
|
this.popScope();
|
|
442
511
|
this.pushScope();
|
|
443
|
-
|
|
512
|
+
p.push(`${this.i()}if (!${enteredVar}) {\n`);
|
|
444
513
|
this.indent++;
|
|
445
|
-
|
|
514
|
+
p.push(this.genBlockStatements(node.elseBody));
|
|
446
515
|
this.indent--;
|
|
447
|
-
|
|
516
|
+
p.push(`\n${this.i()}}\n`);
|
|
448
517
|
this.popScope();
|
|
449
518
|
this.indent--;
|
|
450
|
-
|
|
451
|
-
return
|
|
519
|
+
p.push(`${this.i()}}`);
|
|
520
|
+
return p.join('');
|
|
452
521
|
}
|
|
453
522
|
|
|
454
523
|
this.pushScope();
|
|
455
524
|
for (const v of vars) this.declareVar(v);
|
|
456
|
-
|
|
525
|
+
const p = [];
|
|
457
526
|
if (vars.length === 2) {
|
|
458
|
-
|
|
527
|
+
p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const [${vars[0]}, ${vars[1]}] of ${iterExpr}) {\n`);
|
|
459
528
|
} else {
|
|
460
|
-
|
|
529
|
+
p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const ${vars[0]} of ${iterExpr}) {\n`);
|
|
461
530
|
}
|
|
462
531
|
this.indent++;
|
|
463
|
-
|
|
532
|
+
if (node.guard) {
|
|
533
|
+
p.push(`${this.i()}if (!(${this.genExpression(node.guard)})) continue;\n`);
|
|
534
|
+
}
|
|
535
|
+
p.push(this.genBlockStatements(node.body));
|
|
464
536
|
this.indent--;
|
|
465
|
-
|
|
537
|
+
p.push(`\n${this.i()}}`);
|
|
466
538
|
this.popScope();
|
|
467
539
|
|
|
468
|
-
return
|
|
540
|
+
return p.join('');
|
|
469
541
|
}
|
|
470
542
|
|
|
471
543
|
genWhileStatement(node) {
|
|
472
|
-
|
|
544
|
+
const labelPrefix = node.label ? `${node.label}: ` : '';
|
|
545
|
+
const p = [];
|
|
546
|
+
p.push(`${this.i()}${labelPrefix}while (${this.genExpression(node.condition)}) {\n`);
|
|
473
547
|
this.indent++;
|
|
474
548
|
this.pushScope();
|
|
475
|
-
|
|
549
|
+
p.push(this.genBlockStatements(node.body));
|
|
476
550
|
this.popScope();
|
|
477
551
|
this.indent--;
|
|
478
|
-
|
|
479
|
-
return
|
|
552
|
+
p.push(`\n${this.i()}}`);
|
|
553
|
+
return p.join('');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
genLoopStatement(node) {
|
|
557
|
+
const labelPrefix = node.label ? `${node.label}: ` : '';
|
|
558
|
+
const p = [];
|
|
559
|
+
p.push(`${this.i()}${labelPrefix}while (true) {\n`);
|
|
560
|
+
this.indent++;
|
|
561
|
+
this.pushScope();
|
|
562
|
+
p.push(this.genBlockStatements(node.body));
|
|
563
|
+
this.popScope();
|
|
564
|
+
this.indent--;
|
|
565
|
+
p.push(`\n${this.i()}}`);
|
|
566
|
+
return p.join('');
|
|
480
567
|
}
|
|
481
568
|
|
|
482
569
|
genTryCatchStatement(node) {
|
|
483
|
-
|
|
570
|
+
const p = [];
|
|
571
|
+
p.push(`${this.i()}try {\n`);
|
|
484
572
|
this.indent++;
|
|
485
573
|
this.pushScope();
|
|
486
574
|
for (const stmt of node.tryBody) {
|
|
487
|
-
|
|
575
|
+
p.push(this.generateStatement(stmt) + '\n');
|
|
488
576
|
}
|
|
489
577
|
this.popScope();
|
|
490
578
|
this.indent--;
|
|
491
579
|
|
|
492
580
|
if (node.catchBody) {
|
|
493
581
|
const catchVar = node.catchParam || '__err';
|
|
494
|
-
|
|
582
|
+
p.push(`${this.i()}} catch (${catchVar}) {\n`);
|
|
495
583
|
this.pushScope();
|
|
496
584
|
this.declareVar(catchVar);
|
|
497
585
|
this.indent++;
|
|
498
586
|
// Re-throw propagation sentinels so ? operator works through user try/catch
|
|
499
|
-
|
|
587
|
+
p.push(`${this.i()}if (${catchVar} && ${catchVar}.__tova_propagate) throw ${catchVar};\n`);
|
|
500
588
|
for (const stmt of node.catchBody) {
|
|
501
|
-
|
|
589
|
+
p.push(this.generateStatement(stmt) + '\n');
|
|
502
590
|
}
|
|
503
591
|
this.popScope();
|
|
504
592
|
this.indent--;
|
|
505
|
-
|
|
593
|
+
p.push(`${this.i()}}`);
|
|
506
594
|
}
|
|
507
595
|
|
|
508
596
|
if (node.finallyBody) {
|
|
509
597
|
if (!node.catchBody) {
|
|
510
598
|
// try/finally without catch
|
|
511
|
-
|
|
599
|
+
p.push(`${this.i()}}`);
|
|
512
600
|
}
|
|
513
|
-
|
|
601
|
+
p.push(` finally {\n`);
|
|
514
602
|
this.indent++;
|
|
515
603
|
this.pushScope();
|
|
516
604
|
for (const stmt of node.finallyBody) {
|
|
517
|
-
|
|
605
|
+
p.push(this.generateStatement(stmt) + '\n');
|
|
518
606
|
}
|
|
519
607
|
this.popScope();
|
|
520
608
|
this.indent--;
|
|
521
|
-
|
|
609
|
+
p.push(`${this.i()}}`);
|
|
522
610
|
}
|
|
523
611
|
|
|
524
|
-
return
|
|
612
|
+
return p.join('');
|
|
525
613
|
}
|
|
526
614
|
|
|
527
615
|
genReturnStatement(node) {
|
|
@@ -536,14 +624,15 @@ export class BaseCodegen {
|
|
|
536
624
|
}
|
|
537
625
|
|
|
538
626
|
genBlock(node) {
|
|
539
|
-
|
|
627
|
+
const p = [];
|
|
628
|
+
p.push(`{\n`);
|
|
540
629
|
this.indent++;
|
|
541
630
|
this.pushScope();
|
|
542
|
-
|
|
631
|
+
p.push(this.genBlockStatements(node));
|
|
543
632
|
this.popScope();
|
|
544
633
|
this.indent--;
|
|
545
|
-
|
|
546
|
-
return
|
|
634
|
+
p.push(`\n${this.i()}}`);
|
|
635
|
+
return p.join('');
|
|
547
636
|
}
|
|
548
637
|
|
|
549
638
|
genBlockBody(block) {
|
|
@@ -614,23 +703,24 @@ export class BaseCodegen {
|
|
|
614
703
|
}
|
|
615
704
|
|
|
616
705
|
_genIfStatementWithReturns(node) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
706
|
+
const p = [];
|
|
707
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
708
|
+
p.push(this._genBlockBodyReturns(node.consequent));
|
|
709
|
+
p.push(`\n${this.i()}}`);
|
|
620
710
|
|
|
621
711
|
for (const alt of node.alternates) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
712
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
713
|
+
p.push(this._genBlockBodyReturns(alt.body));
|
|
714
|
+
p.push(`\n${this.i()}}`);
|
|
625
715
|
}
|
|
626
716
|
|
|
627
717
|
if (node.elseBody) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
718
|
+
p.push(` else {\n`);
|
|
719
|
+
p.push(this._genBlockBodyReturns(node.elseBody));
|
|
720
|
+
p.push(`\n${this.i()}}`);
|
|
631
721
|
}
|
|
632
722
|
|
|
633
|
-
return
|
|
723
|
+
return p.join('');
|
|
634
724
|
}
|
|
635
725
|
|
|
636
726
|
_genBlockBodyReturns(block) {
|
|
@@ -691,6 +781,9 @@ export class BaseCodegen {
|
|
|
691
781
|
|
|
692
782
|
// Tova ?? is NaN-safe: catches null, undefined, AND NaN
|
|
693
783
|
if (op === '??') {
|
|
784
|
+
if (this._isSimpleExpression(node.left)) {
|
|
785
|
+
return `((${left} != null && ${left} === ${left}) ? ${left} : ${right})`;
|
|
786
|
+
}
|
|
694
787
|
return `((__tova_v) => (__tova_v != null && __tova_v === __tova_v) ? __tova_v : ${right})(${left})`;
|
|
695
788
|
}
|
|
696
789
|
|
|
@@ -719,6 +812,21 @@ export class BaseCodegen {
|
|
|
719
812
|
const right = this.genExpression(node.operands[1]);
|
|
720
813
|
return `(${left} ${node.operators[0]} ${right})`;
|
|
721
814
|
}
|
|
815
|
+
// Optimization: if all intermediate operands are simple (no side effects),
|
|
816
|
+
// we can inline them without temp vars or IIFE
|
|
817
|
+
const intermediateOperands = node.operands.slice(1, -1);
|
|
818
|
+
const allSimple = intermediateOperands.every(op => this._isSimpleExpression(op));
|
|
819
|
+
|
|
820
|
+
if (allSimple) {
|
|
821
|
+
const parts = [];
|
|
822
|
+
for (let idx = 0; idx < node.operators.length; idx++) {
|
|
823
|
+
const left = this.genExpression(node.operands[idx]);
|
|
824
|
+
const right = this.genExpression(node.operands[idx + 1]);
|
|
825
|
+
parts.push(`(${left} ${node.operators[idx]} ${right})`);
|
|
826
|
+
}
|
|
827
|
+
return `(${parts.join(' && ')})`;
|
|
828
|
+
}
|
|
829
|
+
|
|
722
830
|
const temps = [];
|
|
723
831
|
const parts = [];
|
|
724
832
|
for (let idx = 0; idx < node.operators.length; idx++) {
|
|
@@ -744,9 +852,45 @@ export class BaseCodegen {
|
|
|
744
852
|
return `(${parts.join(' && ')})`;
|
|
745
853
|
}
|
|
746
854
|
|
|
855
|
+
// Try to specialize `in` checks based on the collection's AST type
|
|
856
|
+
_specializeContains(collectionNode, colCode, valCode) {
|
|
857
|
+
switch (collectionNode.type) {
|
|
858
|
+
case 'ArrayLiteral':
|
|
859
|
+
return `${colCode}.includes(${valCode})`;
|
|
860
|
+
case 'StringLiteral':
|
|
861
|
+
case 'TemplateLiteral':
|
|
862
|
+
return `${colCode}.includes(${valCode})`;
|
|
863
|
+
case 'CallExpression':
|
|
864
|
+
if (collectionNode.callee.type === 'MemberExpression' &&
|
|
865
|
+
!collectionNode.callee.computed &&
|
|
866
|
+
collectionNode.callee.property === 'new') {
|
|
867
|
+
const objName = collectionNode.callee.object.type === 'Identifier'
|
|
868
|
+
? collectionNode.callee.object.name : null;
|
|
869
|
+
if (objName === 'Set' || objName === 'Map') {
|
|
870
|
+
return `${colCode}.has(${valCode})`;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
case 'ObjectLiteral':
|
|
875
|
+
return `(${valCode} in ${colCode})`;
|
|
876
|
+
default:
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
747
881
|
genMembershipExpression(node) {
|
|
748
882
|
const val = this.genExpression(node.value);
|
|
749
883
|
const col = this.genExpression(node.collection);
|
|
884
|
+
|
|
885
|
+
// Try specialized check based on collection type
|
|
886
|
+
const specialized = this._specializeContains(node.collection, col, val);
|
|
887
|
+
if (specialized) {
|
|
888
|
+
if (node.negated) {
|
|
889
|
+
return `(!${specialized})`;
|
|
890
|
+
}
|
|
891
|
+
return specialized;
|
|
892
|
+
}
|
|
893
|
+
|
|
750
894
|
this._needsContainsHelper = true;
|
|
751
895
|
if (node.negated) {
|
|
752
896
|
return `(!__contains(${col}, ${val}))`;
|
|
@@ -754,6 +898,41 @@ export class BaseCodegen {
|
|
|
754
898
|
return `__contains(${col}, ${val})`;
|
|
755
899
|
}
|
|
756
900
|
|
|
901
|
+
genIsExpression(node) {
|
|
902
|
+
const val = this.genExpression(node.value);
|
|
903
|
+
const op = node.negated ? '!==' : '===';
|
|
904
|
+
const notOp = node.negated ? '!' : '';
|
|
905
|
+
|
|
906
|
+
// Map Tova type names to JS runtime checks
|
|
907
|
+
switch (node.typeName) {
|
|
908
|
+
case 'String':
|
|
909
|
+
return `(typeof ${val} ${op} 'string')`;
|
|
910
|
+
case 'Int':
|
|
911
|
+
return node.negated
|
|
912
|
+
? `(typeof ${val} !== 'number' || !Number.isInteger(${val}))`
|
|
913
|
+
: `(typeof ${val} === 'number' && Number.isInteger(${val}))`;
|
|
914
|
+
case 'Float':
|
|
915
|
+
return node.negated
|
|
916
|
+
? `(typeof ${val} !== 'number' || Number.isInteger(${val}))`
|
|
917
|
+
: `(typeof ${val} === 'number' && !Number.isInteger(${val}))`;
|
|
918
|
+
case 'Bool':
|
|
919
|
+
return `(typeof ${val} ${op} 'boolean')`;
|
|
920
|
+
case 'Nil':
|
|
921
|
+
return `(${val} ${op} null)`;
|
|
922
|
+
case 'Array':
|
|
923
|
+
return `(${notOp}Array.isArray(${val}))`;
|
|
924
|
+
case 'Function':
|
|
925
|
+
return `(typeof ${val} ${op} 'function')`;
|
|
926
|
+
case 'Number':
|
|
927
|
+
return `(typeof ${val} ${op} 'number')`;
|
|
928
|
+
default:
|
|
929
|
+
// For ADT variants, check __tag; for classes, use instanceof
|
|
930
|
+
return node.negated
|
|
931
|
+
? `(!(${val} != null && (${val}.__tag === '${node.typeName}' || ${val} instanceof (typeof ${node.typeName} !== 'undefined' ? ${node.typeName} : function(){}))))`
|
|
932
|
+
: `(${val} != null && (${val}.__tag === '${node.typeName}' || ${val} instanceof (typeof ${node.typeName} !== 'undefined' ? ${node.typeName} : function(){})))`;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
757
936
|
genCallExpression(node) {
|
|
758
937
|
// Transform Foo.new(...) → new Foo(...)
|
|
759
938
|
if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.property === 'new') {
|
|
@@ -762,20 +941,37 @@ export class BaseCodegen {
|
|
|
762
941
|
return `new ${obj}(${args})`;
|
|
763
942
|
}
|
|
764
943
|
|
|
765
|
-
// Track builtin usage for tree-shaking
|
|
944
|
+
// Track builtin usage for tree-shaking (with dependency resolution)
|
|
766
945
|
if (node.callee.type === 'Identifier') {
|
|
767
946
|
if (BUILTIN_NAMES.has(node.callee.name)) {
|
|
768
|
-
this.
|
|
947
|
+
this._trackBuiltin(node.callee.name);
|
|
769
948
|
}
|
|
770
949
|
if (node.callee.name === 'Ok' || node.callee.name === 'Err' || node.callee.name === 'Some') {
|
|
771
950
|
this._needsResultOption = true;
|
|
772
951
|
}
|
|
952
|
+
// Seq is a dependency of iter — handled by _trackBuiltin via STDLIB_DEPS
|
|
953
|
+
if (node.callee.name === 'iter') {
|
|
954
|
+
this._needsResultOption = true; // Seq.first()/find() return Option
|
|
955
|
+
}
|
|
773
956
|
|
|
774
957
|
// Inline string/collection builtins to direct method calls
|
|
775
958
|
const inlined = this._tryInlineBuiltin(node);
|
|
776
959
|
if (inlined !== null) return inlined;
|
|
777
960
|
}
|
|
778
961
|
|
|
962
|
+
// Track namespace builtin usage: math.sin() → include 'math' namespace + deps
|
|
963
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
964
|
+
node.callee.object.type === 'Identifier' &&
|
|
965
|
+
BUILTIN_NAMES.has(node.callee.object.name)) {
|
|
966
|
+
const ns = node.callee.object.name;
|
|
967
|
+
this._trackBuiltin(ns);
|
|
968
|
+
// Namespaces that depend on Ok/Err need Result/Option
|
|
969
|
+
const deps = STDLIB_DEPS[ns];
|
|
970
|
+
if (deps && (deps.includes('Ok') || deps.includes('Err'))) {
|
|
971
|
+
this._needsResultOption = true;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
779
975
|
// Check for table operation calls with column expressions
|
|
780
976
|
const hasColumnExprs = node.arguments.some(a => this._containsColumnExpr(a));
|
|
781
977
|
if (hasColumnExprs || (node.callee.type === 'Identifier' && ['agg', 'table_agg'].includes(node.callee.name))) {
|
|
@@ -893,6 +1089,10 @@ export class BaseCodegen {
|
|
|
893
1089
|
}
|
|
894
1090
|
|
|
895
1091
|
genMemberExpression(node) {
|
|
1092
|
+
// Track namespace builtin usage: math.PI → include 'math' namespace + deps
|
|
1093
|
+
if (node.object.type === 'Identifier' && BUILTIN_NAMES.has(node.object.name)) {
|
|
1094
|
+
this._trackBuiltin(node.object.name);
|
|
1095
|
+
}
|
|
896
1096
|
const obj = this.genExpression(node.object);
|
|
897
1097
|
if (node.computed) {
|
|
898
1098
|
return `${obj}[${this.genExpression(node.property)}]`;
|
|
@@ -941,7 +1141,14 @@ export class BaseCodegen {
|
|
|
941
1141
|
if (placeholderCount > 0) {
|
|
942
1142
|
const callee = this.genExpression(right.callee);
|
|
943
1143
|
if (placeholderCount > 1) {
|
|
944
|
-
// Multiple placeholders:
|
|
1144
|
+
// Multiple placeholders: inline if left is simple, otherwise IIFE temp var
|
|
1145
|
+
if (this._isSimpleExpression(node.left)) {
|
|
1146
|
+
const args = right.arguments.map(a => {
|
|
1147
|
+
if (a.type === 'Identifier' && a.name === '_') return left;
|
|
1148
|
+
return this.genExpression(a);
|
|
1149
|
+
}).join(', ');
|
|
1150
|
+
return `${callee}(${args})`;
|
|
1151
|
+
}
|
|
945
1152
|
const tmp = `__pipe_${this._uid()}`;
|
|
946
1153
|
const args = right.arguments.map(a => {
|
|
947
1154
|
if (a.type === 'Identifier' && a.name === '_') return tmp;
|
|
@@ -1227,9 +1434,18 @@ export class BaseCodegen {
|
|
|
1227
1434
|
const body = this.genBlockBody(node.body);
|
|
1228
1435
|
this.popScope();
|
|
1229
1436
|
if (hasPropagate) {
|
|
1230
|
-
|
|
1437
|
+
const p = [];
|
|
1438
|
+
p.push(`${asyncPrefix}(${params}) => {`);
|
|
1439
|
+
p.push(`${this.i()} try {`);
|
|
1440
|
+
p.push(body);
|
|
1441
|
+
p.push(`${this.i()} } catch (__e) {`);
|
|
1442
|
+
p.push(`${this.i()} if (__e && __e.__tova_propagate) return __e.value;`);
|
|
1443
|
+
p.push(`${this.i()} throw __e;`);
|
|
1444
|
+
p.push(`${this.i()} }`);
|
|
1445
|
+
p.push(`${this.i()}}`);
|
|
1446
|
+
return p.join('\n');
|
|
1231
1447
|
}
|
|
1232
|
-
return `${asyncPrefix}(${params}) => {
|
|
1448
|
+
return [`${asyncPrefix}(${params}) => {`, body, `${this.i()}}`].join('\n');
|
|
1233
1449
|
}
|
|
1234
1450
|
|
|
1235
1451
|
// Statement bodies (compound assignment, assignment in lambda)
|
|
@@ -1249,12 +1465,73 @@ export class BaseCodegen {
|
|
|
1249
1465
|
return `${asyncPrefix}(${params}) => ${this.genExpression(node.body)}`;
|
|
1250
1466
|
}
|
|
1251
1467
|
|
|
1468
|
+
// Check if a match can be emitted as a ternary chain instead of IIFE
|
|
1469
|
+
_isSimpleMatch(node) {
|
|
1470
|
+
if (!this._isSimpleExpression(node.subject)) return false;
|
|
1471
|
+
for (const arm of node.arms) {
|
|
1472
|
+
// All bodies must be expressions (not block statements)
|
|
1473
|
+
if (arm.body.type === 'BlockStatement') return false;
|
|
1474
|
+
// No patterns that need variable bindings
|
|
1475
|
+
if (this._patternNeedsBindings(arm.pattern)) return false;
|
|
1476
|
+
// Guards with BindingPattern need IIFE for binding
|
|
1477
|
+
if (arm.guard && arm.pattern.type === 'BindingPattern') return false;
|
|
1478
|
+
}
|
|
1479
|
+
return true;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// Check recursively whether a pattern requires const bindings
|
|
1483
|
+
_patternNeedsBindings(pattern) {
|
|
1484
|
+
switch (pattern.type) {
|
|
1485
|
+
case 'LiteralPattern':
|
|
1486
|
+
case 'WildcardPattern':
|
|
1487
|
+
case 'RangePattern':
|
|
1488
|
+
return false;
|
|
1489
|
+
case 'BindingPattern':
|
|
1490
|
+
return true;
|
|
1491
|
+
case 'VariantPattern':
|
|
1492
|
+
return pattern.fields.some(f => typeof f === 'string' || (f && this._patternNeedsBindings(f)));
|
|
1493
|
+
case 'ArrayPattern':
|
|
1494
|
+
case 'TuplePattern':
|
|
1495
|
+
return pattern.elements.some(el => el && this._patternNeedsBindings(el));
|
|
1496
|
+
default:
|
|
1497
|
+
return true; // Conservative: unknown patterns may need bindings
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Generate a simple match as nested ternary
|
|
1502
|
+
_genSimpleMatch(node) {
|
|
1503
|
+
const subject = this.genExpression(node.subject);
|
|
1504
|
+
let result = '';
|
|
1505
|
+
for (let idx = 0; idx < node.arms.length; idx++) {
|
|
1506
|
+
const arm = node.arms[idx];
|
|
1507
|
+
const body = this.genExpression(arm.body);
|
|
1508
|
+
// Last arm with wildcard → else branch
|
|
1509
|
+
if ((arm.pattern.type === 'WildcardPattern' || arm.pattern.type === 'BindingPattern') && !arm.guard) {
|
|
1510
|
+
result += body;
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
const condition = this.genPatternCondition(arm.pattern, subject, arm.guard);
|
|
1514
|
+
result += `(${condition}) ? ${body} : `;
|
|
1515
|
+
// If this is the last arm and not a wildcard, add undefined as fallback
|
|
1516
|
+
if (idx === node.arms.length - 1) {
|
|
1517
|
+
result += 'undefined';
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return `(${result})`;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1252
1523
|
genMatchExpression(node) {
|
|
1524
|
+
// Optimization: simple matches emit ternary chain instead of IIFE
|
|
1525
|
+
if (this._isSimpleMatch(node)) {
|
|
1526
|
+
return this._genSimpleMatch(node);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1253
1529
|
// Generate as IIFE with if-else chain
|
|
1254
1530
|
const subject = this.genExpression(node.subject);
|
|
1255
1531
|
const tempVar = '__match';
|
|
1256
1532
|
|
|
1257
|
-
|
|
1533
|
+
const p = [];
|
|
1534
|
+
p.push(`((${tempVar}) => {\n`);
|
|
1258
1535
|
this.indent++;
|
|
1259
1536
|
|
|
1260
1537
|
for (let idx = 0; idx < node.arms.length; idx++) {
|
|
@@ -1265,36 +1542,36 @@ export class BaseCodegen {
|
|
|
1265
1542
|
if (idx === node.arms.length - 1 && !arm.guard) {
|
|
1266
1543
|
// Default case
|
|
1267
1544
|
if (arm.pattern.type === 'BindingPattern') {
|
|
1268
|
-
|
|
1545
|
+
p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
|
|
1269
1546
|
}
|
|
1270
1547
|
if (arm.body.type === 'BlockStatement') {
|
|
1271
|
-
|
|
1548
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1272
1549
|
} else {
|
|
1273
|
-
|
|
1550
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1274
1551
|
}
|
|
1275
1552
|
break;
|
|
1276
1553
|
}
|
|
1277
1554
|
}
|
|
1278
1555
|
|
|
1279
1556
|
const keyword = idx === 0 ? 'if' : 'else if';
|
|
1280
|
-
|
|
1557
|
+
p.push(`${this.i()}${keyword} (${condition}) {\n`);
|
|
1281
1558
|
this.indent++;
|
|
1282
1559
|
|
|
1283
1560
|
// Bind variables from pattern
|
|
1284
|
-
|
|
1561
|
+
p.push(this.genPatternBindings(arm.pattern, tempVar));
|
|
1285
1562
|
|
|
1286
1563
|
if (arm.body.type === 'BlockStatement') {
|
|
1287
|
-
|
|
1564
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1288
1565
|
} else {
|
|
1289
|
-
|
|
1566
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1290
1567
|
}
|
|
1291
1568
|
this.indent--;
|
|
1292
|
-
|
|
1569
|
+
p.push(`${this.i()}}\n`);
|
|
1293
1570
|
}
|
|
1294
1571
|
|
|
1295
1572
|
this.indent--;
|
|
1296
|
-
|
|
1297
|
-
return
|
|
1573
|
+
p.push(`${this.i()}})(${subject})`);
|
|
1574
|
+
return p.join('');
|
|
1298
1575
|
}
|
|
1299
1576
|
|
|
1300
1577
|
genIfExpression(node) {
|
|
@@ -1309,27 +1586,40 @@ export class BaseCodegen {
|
|
|
1309
1586
|
return `((${cond}) ? (${thenExpr}) : (${elseExpr}))`;
|
|
1310
1587
|
}
|
|
1311
1588
|
|
|
1589
|
+
// Extended optimization: if/elif/else where ALL branches are single expressions → nested ternary
|
|
1590
|
+
if (node.alternates.length > 0 && node.elseBody &&
|
|
1591
|
+
isSingleExpr(node.consequent) && isSingleExpr(node.elseBody) &&
|
|
1592
|
+
node.alternates.every(alt => isSingleExpr(alt.body))) {
|
|
1593
|
+
let result = `((${this.genExpression(node.condition)}) ? (${this.genExpression(node.consequent.body[0].expression)})`;
|
|
1594
|
+
for (const alt of node.alternates) {
|
|
1595
|
+
result += ` : (${this.genExpression(alt.condition)}) ? (${this.genExpression(alt.body.body[0].expression)})`;
|
|
1596
|
+
}
|
|
1597
|
+
result += ` : (${this.genExpression(node.elseBody.body[0].expression)}))`;
|
|
1598
|
+
return result;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1312
1601
|
// Full IIFE for multi-statement branches
|
|
1313
|
-
|
|
1602
|
+
const p = [];
|
|
1603
|
+
p.push(`(() => {\n`);
|
|
1314
1604
|
this.indent++;
|
|
1315
1605
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1606
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
1607
|
+
p.push(this.genBlockBody(node.consequent));
|
|
1608
|
+
p.push(`\n${this.i()}}`);
|
|
1319
1609
|
|
|
1320
1610
|
for (const alt of node.alternates) {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1611
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
1612
|
+
p.push(this.genBlockBody(alt.body));
|
|
1613
|
+
p.push(`\n${this.i()}}`);
|
|
1324
1614
|
}
|
|
1325
1615
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1616
|
+
p.push(` else {\n`);
|
|
1617
|
+
p.push(this.genBlockBody(node.elseBody));
|
|
1618
|
+
p.push(`\n${this.i()}}`);
|
|
1329
1619
|
|
|
1330
1620
|
this.indent--;
|
|
1331
|
-
|
|
1332
|
-
return
|
|
1621
|
+
p.push(`\n${this.i()}})()`);
|
|
1622
|
+
return p.join('');
|
|
1333
1623
|
}
|
|
1334
1624
|
|
|
1335
1625
|
genPatternCondition(pattern, subject, guard) {
|
|
@@ -1546,14 +1836,15 @@ export class BaseCodegen {
|
|
|
1546
1836
|
}
|
|
1547
1837
|
|
|
1548
1838
|
genGuardStatement(node) {
|
|
1549
|
-
|
|
1839
|
+
const p = [];
|
|
1840
|
+
p.push(`${this.i()}if (!(${this.genExpression(node.condition)})) {\n`);
|
|
1550
1841
|
this.indent++;
|
|
1551
1842
|
this.pushScope();
|
|
1552
|
-
|
|
1843
|
+
p.push(this.genBlockStatements(node.elseBody));
|
|
1553
1844
|
this.popScope();
|
|
1554
1845
|
this.indent--;
|
|
1555
|
-
|
|
1556
|
-
return
|
|
1846
|
+
p.push(`\n${this.i()}}`);
|
|
1847
|
+
return p.join('');
|
|
1557
1848
|
}
|
|
1558
1849
|
|
|
1559
1850
|
genInterfaceDeclaration(node) {
|
|
@@ -1605,6 +1896,7 @@ export class BaseCodegen {
|
|
|
1605
1896
|
const targetName = hasVariants ? null : node.name;
|
|
1606
1897
|
const fieldNames = hasVariants ? [] : node.variants.map(f => f.name);
|
|
1607
1898
|
|
|
1899
|
+
const builtinTraits = new Set(['Eq', 'Show', 'JSON']);
|
|
1608
1900
|
for (const trait of node.derive) {
|
|
1609
1901
|
if (trait === 'Eq' && targetName) {
|
|
1610
1902
|
// Deep equality: compare all fields
|
|
@@ -1619,6 +1911,19 @@ export class BaseCodegen {
|
|
|
1619
1911
|
lines.push(`${this.i()}${targetName}.toJSON = function(obj) { return JSON.stringify(obj); };`);
|
|
1620
1912
|
lines.push(`${this.i()}${targetName}.fromJSON = function(str) { const d = JSON.parse(str); return ${targetName}(${fieldNames.map(f => `d.${f}`).join(', ')}); };`);
|
|
1621
1913
|
}
|
|
1914
|
+
|
|
1915
|
+
// Extensible derive: user-defined traits
|
|
1916
|
+
if (!builtinTraits.has(trait) && targetName) {
|
|
1917
|
+
const traitDecl = this._traitDecls.get(trait);
|
|
1918
|
+
if (traitDecl) {
|
|
1919
|
+
for (const method of traitDecl.methods) {
|
|
1920
|
+
if (method.body) {
|
|
1921
|
+
// Trait has a default implementation — use it
|
|
1922
|
+
lines.push(`${this.i()}${targetName}.prototype.${method.name} = __trait_${trait}_${method.name};`);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1622
1927
|
}
|
|
1623
1928
|
|
|
1624
1929
|
// For variant types with derive
|
|
@@ -1641,6 +1946,11 @@ export class BaseCodegen {
|
|
|
1641
1946
|
}
|
|
1642
1947
|
|
|
1643
1948
|
genImplDeclaration(node) {
|
|
1949
|
+
// Register trait impl for extensible derive
|
|
1950
|
+
if (node.traitName) {
|
|
1951
|
+
this._traitImpls.set(`${node.traitName}:${node.typeName}`, node);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1644
1954
|
const lines = [];
|
|
1645
1955
|
for (const method of node.methods) {
|
|
1646
1956
|
const hasSelf = method.params.some(p => p.name === 'self');
|
|
@@ -1667,6 +1977,9 @@ export class BaseCodegen {
|
|
|
1667
1977
|
}
|
|
1668
1978
|
|
|
1669
1979
|
genTraitDeclaration(node) {
|
|
1980
|
+
// Register trait for extensible derive
|
|
1981
|
+
this._traitDecls.set(node.name, { methods: node.methods });
|
|
1982
|
+
|
|
1670
1983
|
// Traits are mostly compile-time, but generate default implementations as functions
|
|
1671
1984
|
const lines = [];
|
|
1672
1985
|
const defaultMethods = node.methods.filter(m => m.body);
|
|
@@ -1692,8 +2005,31 @@ export class BaseCodegen {
|
|
|
1692
2005
|
genTypeAlias(node) {
|
|
1693
2006
|
// Type aliases are compile-time only
|
|
1694
2007
|
const exportStr = node.isPublic ? 'export ' : '';
|
|
1695
|
-
const
|
|
1696
|
-
|
|
2008
|
+
const typeParams = node.typeParams && node.typeParams.length > 0 ? `<${node.typeParams.join(', ')}>` : '';
|
|
2009
|
+
const typeStr = this._typeAnnotationToString(node.typeExpr);
|
|
2010
|
+
return `${this.i()}/* ${exportStr}type alias: ${node.name}${typeParams} = ${typeStr} */`;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
_typeAnnotationToString(ann) {
|
|
2014
|
+
if (!ann) return 'any';
|
|
2015
|
+
if (ann.type === 'UnionTypeAnnotation') {
|
|
2016
|
+
return ann.members.map(m => this._typeAnnotationToString(m)).join(' | ');
|
|
2017
|
+
}
|
|
2018
|
+
if (ann.type === 'ArrayTypeAnnotation') {
|
|
2019
|
+
return `[${this._typeAnnotationToString(ann.elementType)}]`;
|
|
2020
|
+
}
|
|
2021
|
+
if (ann.type === 'TupleTypeAnnotation') {
|
|
2022
|
+
return `(${ann.elementTypes.map(t => this._typeAnnotationToString(t)).join(', ')})`;
|
|
2023
|
+
}
|
|
2024
|
+
if (ann.type === 'FunctionTypeAnnotation') {
|
|
2025
|
+
const params = ann.paramTypes.map(t => this._typeAnnotationToString(t)).join(', ');
|
|
2026
|
+
return `(${params}) -> ${this._typeAnnotationToString(ann.returnType)}`;
|
|
2027
|
+
}
|
|
2028
|
+
if (ann.typeParams && ann.typeParams.length > 0) {
|
|
2029
|
+
const params = ann.typeParams.map(t => this._typeAnnotationToString(t)).join(', ');
|
|
2030
|
+
return `${ann.name}<${params}>`;
|
|
2031
|
+
}
|
|
2032
|
+
return ann.name || 'any';
|
|
1697
2033
|
}
|
|
1698
2034
|
|
|
1699
2035
|
genRefinementType(node) {
|
|
@@ -1711,6 +2047,28 @@ export class BaseCodegen {
|
|
|
1711
2047
|
return `${this.i()}/* defer */`;
|
|
1712
2048
|
}
|
|
1713
2049
|
|
|
2050
|
+
genWithStatement(node) {
|
|
2051
|
+
const expr = this.genExpression(node.expression);
|
|
2052
|
+
const name = node.name;
|
|
2053
|
+
this.declareVar(name);
|
|
2054
|
+
const p = [];
|
|
2055
|
+
p.push(`${this.i()}const ${name} = ${expr};`);
|
|
2056
|
+
p.push(`${this.i()}try {`);
|
|
2057
|
+
this.indent++;
|
|
2058
|
+
this.pushScope();
|
|
2059
|
+
p.push(this.genBlockStatements(node.body));
|
|
2060
|
+
this.popScope();
|
|
2061
|
+
this.indent--;
|
|
2062
|
+
p.push(`${this.i()}} finally {`);
|
|
2063
|
+
this.indent++;
|
|
2064
|
+
p.push(`${this.i()}if (${name} != null && typeof ${name}.close === 'function') ${name}.close();`);
|
|
2065
|
+
p.push(`${this.i()}else if (${name} != null && typeof ${name}.dispose === 'function') ${name}.dispose();`);
|
|
2066
|
+
p.push(`${this.i()}else if (${name} != null && typeof ${name}[Symbol.dispose] === 'function') ${name}[Symbol.dispose]();`);
|
|
2067
|
+
this.indent--;
|
|
2068
|
+
p.push(`${this.i()}}`);
|
|
2069
|
+
return p.join('\n');
|
|
2070
|
+
}
|
|
2071
|
+
|
|
1714
2072
|
// Check if a function body contains yield expressions (for generator detection)
|
|
1715
2073
|
_containsYield(node) {
|
|
1716
2074
|
if (!node) return false;
|