tova 0.2.9 → 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 +1404 -114
- package/package.json +3 -1
- package/src/analyzer/analyzer.js +882 -695
- 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 +473 -111
- package/src/codegen/client-codegen.js +109 -46
- package/src/codegen/codegen.js +65 -5
- package/src/codegen/server-codegen.js +297 -38
- 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 +306 -64
- package/src/lexer/tokens.js +19 -0
- package/src/lsp/server.js +935 -53
- 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 +492 -1056
- 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 +239 -42
- package/src/stdlib/advanced-collections.js +81 -0
- package/src/stdlib/inline.js +556 -13
- 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) {
|
|
@@ -679,14 +769,21 @@ export class BaseCodegen {
|
|
|
679
769
|
const right = this.genExpression(node.right);
|
|
680
770
|
const op = node.operator;
|
|
681
771
|
|
|
682
|
-
// String multiply: "ha" * 3 => "ha".repeat(3)
|
|
772
|
+
// String multiply: "ha" * 3 => "ha".repeat(3), also x * 3 when x is string
|
|
683
773
|
if (op === '*' &&
|
|
684
774
|
(node.left.type === 'StringLiteral' || node.left.type === 'TemplateLiteral')) {
|
|
685
775
|
return `${left}.repeat(${right})`;
|
|
686
776
|
}
|
|
777
|
+
if (op === '*' &&
|
|
778
|
+
(node.right.type === 'StringLiteral' || node.right.type === 'TemplateLiteral')) {
|
|
779
|
+
return `${right}.repeat(${left})`;
|
|
780
|
+
}
|
|
687
781
|
|
|
688
782
|
// Tova ?? is NaN-safe: catches null, undefined, AND NaN
|
|
689
783
|
if (op === '??') {
|
|
784
|
+
if (this._isSimpleExpression(node.left)) {
|
|
785
|
+
return `((${left} != null && ${left} === ${left}) ? ${left} : ${right})`;
|
|
786
|
+
}
|
|
690
787
|
return `((__tova_v) => (__tova_v != null && __tova_v === __tova_v) ? __tova_v : ${right})(${left})`;
|
|
691
788
|
}
|
|
692
789
|
|
|
@@ -715,6 +812,21 @@ export class BaseCodegen {
|
|
|
715
812
|
const right = this.genExpression(node.operands[1]);
|
|
716
813
|
return `(${left} ${node.operators[0]} ${right})`;
|
|
717
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
|
+
|
|
718
830
|
const temps = [];
|
|
719
831
|
const parts = [];
|
|
720
832
|
for (let idx = 0; idx < node.operators.length; idx++) {
|
|
@@ -740,9 +852,45 @@ export class BaseCodegen {
|
|
|
740
852
|
return `(${parts.join(' && ')})`;
|
|
741
853
|
}
|
|
742
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
|
+
|
|
743
881
|
genMembershipExpression(node) {
|
|
744
882
|
const val = this.genExpression(node.value);
|
|
745
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
|
+
|
|
746
894
|
this._needsContainsHelper = true;
|
|
747
895
|
if (node.negated) {
|
|
748
896
|
return `(!__contains(${col}, ${val}))`;
|
|
@@ -750,6 +898,41 @@ export class BaseCodegen {
|
|
|
750
898
|
return `__contains(${col}, ${val})`;
|
|
751
899
|
}
|
|
752
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
|
+
|
|
753
936
|
genCallExpression(node) {
|
|
754
937
|
// Transform Foo.new(...) → new Foo(...)
|
|
755
938
|
if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.property === 'new') {
|
|
@@ -758,20 +941,37 @@ export class BaseCodegen {
|
|
|
758
941
|
return `new ${obj}(${args})`;
|
|
759
942
|
}
|
|
760
943
|
|
|
761
|
-
// Track builtin usage for tree-shaking
|
|
944
|
+
// Track builtin usage for tree-shaking (with dependency resolution)
|
|
762
945
|
if (node.callee.type === 'Identifier') {
|
|
763
946
|
if (BUILTIN_NAMES.has(node.callee.name)) {
|
|
764
|
-
this.
|
|
947
|
+
this._trackBuiltin(node.callee.name);
|
|
765
948
|
}
|
|
766
949
|
if (node.callee.name === 'Ok' || node.callee.name === 'Err' || node.callee.name === 'Some') {
|
|
767
950
|
this._needsResultOption = true;
|
|
768
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
|
+
}
|
|
769
956
|
|
|
770
957
|
// Inline string/collection builtins to direct method calls
|
|
771
958
|
const inlined = this._tryInlineBuiltin(node);
|
|
772
959
|
if (inlined !== null) return inlined;
|
|
773
960
|
}
|
|
774
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
|
+
|
|
775
975
|
// Check for table operation calls with column expressions
|
|
776
976
|
const hasColumnExprs = node.arguments.some(a => this._containsColumnExpr(a));
|
|
777
977
|
if (hasColumnExprs || (node.callee.type === 'Identifier' && ['agg', 'table_agg'].includes(node.callee.name))) {
|
|
@@ -889,6 +1089,10 @@ export class BaseCodegen {
|
|
|
889
1089
|
}
|
|
890
1090
|
|
|
891
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
|
+
}
|
|
892
1096
|
const obj = this.genExpression(node.object);
|
|
893
1097
|
if (node.computed) {
|
|
894
1098
|
return `${obj}[${this.genExpression(node.property)}]`;
|
|
@@ -937,7 +1141,14 @@ export class BaseCodegen {
|
|
|
937
1141
|
if (placeholderCount > 0) {
|
|
938
1142
|
const callee = this.genExpression(right.callee);
|
|
939
1143
|
if (placeholderCount > 1) {
|
|
940
|
-
// 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
|
+
}
|
|
941
1152
|
const tmp = `__pipe_${this._uid()}`;
|
|
942
1153
|
const args = right.arguments.map(a => {
|
|
943
1154
|
if (a.type === 'Identifier' && a.name === '_') return tmp;
|
|
@@ -1027,7 +1238,7 @@ export class BaseCodegen {
|
|
|
1027
1238
|
if (node.type === 'TemplateLiteral') {
|
|
1028
1239
|
// Template literal with column references
|
|
1029
1240
|
const parts = node.parts.map(p => {
|
|
1030
|
-
if (p.type === 'text') return p.value;
|
|
1241
|
+
if (p.type === 'text') return p.value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
1031
1242
|
return `\${${this._genColumnBody(p.value)}}`;
|
|
1032
1243
|
});
|
|
1033
1244
|
return '`' + parts.join('') + '`';
|
|
@@ -1223,9 +1434,18 @@ export class BaseCodegen {
|
|
|
1223
1434
|
const body = this.genBlockBody(node.body);
|
|
1224
1435
|
this.popScope();
|
|
1225
1436
|
if (hasPropagate) {
|
|
1226
|
-
|
|
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');
|
|
1227
1447
|
}
|
|
1228
|
-
return `${asyncPrefix}(${params}) => {
|
|
1448
|
+
return [`${asyncPrefix}(${params}) => {`, body, `${this.i()}}`].join('\n');
|
|
1229
1449
|
}
|
|
1230
1450
|
|
|
1231
1451
|
// Statement bodies (compound assignment, assignment in lambda)
|
|
@@ -1245,12 +1465,73 @@ export class BaseCodegen {
|
|
|
1245
1465
|
return `${asyncPrefix}(${params}) => ${this.genExpression(node.body)}`;
|
|
1246
1466
|
}
|
|
1247
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
|
+
|
|
1248
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
|
+
|
|
1249
1529
|
// Generate as IIFE with if-else chain
|
|
1250
1530
|
const subject = this.genExpression(node.subject);
|
|
1251
1531
|
const tempVar = '__match';
|
|
1252
1532
|
|
|
1253
|
-
|
|
1533
|
+
const p = [];
|
|
1534
|
+
p.push(`((${tempVar}) => {\n`);
|
|
1254
1535
|
this.indent++;
|
|
1255
1536
|
|
|
1256
1537
|
for (let idx = 0; idx < node.arms.length; idx++) {
|
|
@@ -1261,36 +1542,36 @@ export class BaseCodegen {
|
|
|
1261
1542
|
if (idx === node.arms.length - 1 && !arm.guard) {
|
|
1262
1543
|
// Default case
|
|
1263
1544
|
if (arm.pattern.type === 'BindingPattern') {
|
|
1264
|
-
|
|
1545
|
+
p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
|
|
1265
1546
|
}
|
|
1266
1547
|
if (arm.body.type === 'BlockStatement') {
|
|
1267
|
-
|
|
1548
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1268
1549
|
} else {
|
|
1269
|
-
|
|
1550
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1270
1551
|
}
|
|
1271
1552
|
break;
|
|
1272
1553
|
}
|
|
1273
1554
|
}
|
|
1274
1555
|
|
|
1275
1556
|
const keyword = idx === 0 ? 'if' : 'else if';
|
|
1276
|
-
|
|
1557
|
+
p.push(`${this.i()}${keyword} (${condition}) {\n`);
|
|
1277
1558
|
this.indent++;
|
|
1278
1559
|
|
|
1279
1560
|
// Bind variables from pattern
|
|
1280
|
-
|
|
1561
|
+
p.push(this.genPatternBindings(arm.pattern, tempVar));
|
|
1281
1562
|
|
|
1282
1563
|
if (arm.body.type === 'BlockStatement') {
|
|
1283
|
-
|
|
1564
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1284
1565
|
} else {
|
|
1285
|
-
|
|
1566
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1286
1567
|
}
|
|
1287
1568
|
this.indent--;
|
|
1288
|
-
|
|
1569
|
+
p.push(`${this.i()}}\n`);
|
|
1289
1570
|
}
|
|
1290
1571
|
|
|
1291
1572
|
this.indent--;
|
|
1292
|
-
|
|
1293
|
-
return
|
|
1573
|
+
p.push(`${this.i()}})(${subject})`);
|
|
1574
|
+
return p.join('');
|
|
1294
1575
|
}
|
|
1295
1576
|
|
|
1296
1577
|
genIfExpression(node) {
|
|
@@ -1305,27 +1586,40 @@ export class BaseCodegen {
|
|
|
1305
1586
|
return `((${cond}) ? (${thenExpr}) : (${elseExpr}))`;
|
|
1306
1587
|
}
|
|
1307
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
|
+
|
|
1308
1601
|
// Full IIFE for multi-statement branches
|
|
1309
|
-
|
|
1602
|
+
const p = [];
|
|
1603
|
+
p.push(`(() => {\n`);
|
|
1310
1604
|
this.indent++;
|
|
1311
1605
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1606
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
1607
|
+
p.push(this.genBlockBody(node.consequent));
|
|
1608
|
+
p.push(`\n${this.i()}}`);
|
|
1315
1609
|
|
|
1316
1610
|
for (const alt of node.alternates) {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1611
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
1612
|
+
p.push(this.genBlockBody(alt.body));
|
|
1613
|
+
p.push(`\n${this.i()}}`);
|
|
1320
1614
|
}
|
|
1321
1615
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1616
|
+
p.push(` else {\n`);
|
|
1617
|
+
p.push(this.genBlockBody(node.elseBody));
|
|
1618
|
+
p.push(`\n${this.i()}}`);
|
|
1325
1619
|
|
|
1326
1620
|
this.indent--;
|
|
1327
|
-
|
|
1328
|
-
return
|
|
1621
|
+
p.push(`\n${this.i()}})()`);
|
|
1622
|
+
return p.join('');
|
|
1329
1623
|
}
|
|
1330
1624
|
|
|
1331
1625
|
genPatternCondition(pattern, subject, guard) {
|
|
@@ -1542,14 +1836,15 @@ export class BaseCodegen {
|
|
|
1542
1836
|
}
|
|
1543
1837
|
|
|
1544
1838
|
genGuardStatement(node) {
|
|
1545
|
-
|
|
1839
|
+
const p = [];
|
|
1840
|
+
p.push(`${this.i()}if (!(${this.genExpression(node.condition)})) {\n`);
|
|
1546
1841
|
this.indent++;
|
|
1547
1842
|
this.pushScope();
|
|
1548
|
-
|
|
1843
|
+
p.push(this.genBlockStatements(node.elseBody));
|
|
1549
1844
|
this.popScope();
|
|
1550
1845
|
this.indent--;
|
|
1551
|
-
|
|
1552
|
-
return
|
|
1846
|
+
p.push(`\n${this.i()}}`);
|
|
1847
|
+
return p.join('');
|
|
1553
1848
|
}
|
|
1554
1849
|
|
|
1555
1850
|
genInterfaceDeclaration(node) {
|
|
@@ -1601,6 +1896,7 @@ export class BaseCodegen {
|
|
|
1601
1896
|
const targetName = hasVariants ? null : node.name;
|
|
1602
1897
|
const fieldNames = hasVariants ? [] : node.variants.map(f => f.name);
|
|
1603
1898
|
|
|
1899
|
+
const builtinTraits = new Set(['Eq', 'Show', 'JSON']);
|
|
1604
1900
|
for (const trait of node.derive) {
|
|
1605
1901
|
if (trait === 'Eq' && targetName) {
|
|
1606
1902
|
// Deep equality: compare all fields
|
|
@@ -1615,6 +1911,19 @@ export class BaseCodegen {
|
|
|
1615
1911
|
lines.push(`${this.i()}${targetName}.toJSON = function(obj) { return JSON.stringify(obj); };`);
|
|
1616
1912
|
lines.push(`${this.i()}${targetName}.fromJSON = function(str) { const d = JSON.parse(str); return ${targetName}(${fieldNames.map(f => `d.${f}`).join(', ')}); };`);
|
|
1617
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
|
+
}
|
|
1618
1927
|
}
|
|
1619
1928
|
|
|
1620
1929
|
// For variant types with derive
|
|
@@ -1637,6 +1946,11 @@ export class BaseCodegen {
|
|
|
1637
1946
|
}
|
|
1638
1947
|
|
|
1639
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
|
+
|
|
1640
1954
|
const lines = [];
|
|
1641
1955
|
for (const method of node.methods) {
|
|
1642
1956
|
const hasSelf = method.params.some(p => p.name === 'self');
|
|
@@ -1663,6 +1977,9 @@ export class BaseCodegen {
|
|
|
1663
1977
|
}
|
|
1664
1978
|
|
|
1665
1979
|
genTraitDeclaration(node) {
|
|
1980
|
+
// Register trait for extensible derive
|
|
1981
|
+
this._traitDecls.set(node.name, { methods: node.methods });
|
|
1982
|
+
|
|
1666
1983
|
// Traits are mostly compile-time, but generate default implementations as functions
|
|
1667
1984
|
const lines = [];
|
|
1668
1985
|
const defaultMethods = node.methods.filter(m => m.body);
|
|
@@ -1688,8 +2005,31 @@ export class BaseCodegen {
|
|
|
1688
2005
|
genTypeAlias(node) {
|
|
1689
2006
|
// Type aliases are compile-time only
|
|
1690
2007
|
const exportStr = node.isPublic ? 'export ' : '';
|
|
1691
|
-
const
|
|
1692
|
-
|
|
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';
|
|
1693
2033
|
}
|
|
1694
2034
|
|
|
1695
2035
|
genRefinementType(node) {
|
|
@@ -1707,6 +2047,28 @@ export class BaseCodegen {
|
|
|
1707
2047
|
return `${this.i()}/* defer */`;
|
|
1708
2048
|
}
|
|
1709
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
|
+
|
|
1710
2072
|
// Check if a function body contains yield expressions (for generator detection)
|
|
1711
2073
|
_containsYield(node) {
|
|
1712
2074
|
if (!node) return false;
|