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.
@@ -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
- this._sourceMappings.push({
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 'function __contains(col, val) {\n' +
84
- ' if (Array.isArray(col) || typeof col === \'string\') return col.includes(val);\n' +
85
- ' if (col instanceof Set || col instanceof Map) return col.has(val);\n' +
86
- ' if (typeof col === \'object\' && col !== null) return val in col;\n' +
87
- ' return false;\n' +
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
- const newlines = result.split('\n').length - 1;
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
- return `${this.i()}${exportPrefix}${asyncPrefix}function${genStar} ${node.name}(${params}) {\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}}`;
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
- return `${this.i()}${exportPrefix}${asyncPrefix}function${genStar} ${node.name}(${params}) {\n${body}\n${this.i()}}`;
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
- let code = `${this.i()}if (${this.genExpression(node.condition)}) {\n`;
448
+ const p = [];
449
+ p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
387
450
  this.indent++;
388
451
  this.pushScope();
389
- code += this.genBlockStatements(node.consequent);
452
+ p.push(this.genBlockStatements(node.consequent));
390
453
  this.popScope();
391
454
  this.indent--;
392
- code += `\n${this.i()}}`;
455
+ p.push(`\n${this.i()}}`);
393
456
 
394
457
  for (const alt of node.alternates) {
395
- code += ` else if (${this.genExpression(alt.condition)}) {\n`;
458
+ p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
396
459
  this.indent++;
397
460
  this.pushScope();
398
- code += this.genBlockStatements(alt.body);
461
+ p.push(this.genBlockStatements(alt.body));
399
462
  this.popScope();
400
463
  this.indent--;
401
- code += `\n${this.i()}}`;
464
+ p.push(`\n${this.i()}}`);
402
465
  }
403
466
 
404
467
  if (node.elseBody) {
405
- code += ` else {\n`;
468
+ p.push(` else {\n`);
406
469
  this.indent++;
407
470
  this.pushScope();
408
- code += this.genBlockStatements(node.elseBody);
471
+ p.push(this.genBlockStatements(node.elseBody));
409
472
  this.popScope();
410
473
  this.indent--;
411
- code += `\n${this.i()}}`;
474
+ p.push(`\n${this.i()}}`);
412
475
  }
413
476
 
414
- return code;
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
- let code = `${this.i()}{\n`;
490
+ const p = [];
491
+ p.push(`${this.i()}{\n`);
426
492
  this.indent++;
427
- code += `${this.i()}const ${tempVar} = ${iterExpr};\n`;
428
- code += `${this.i()}let ${enteredVar} = false;\n`;
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
- code += `${this.i()}for (const [${vars[0]}, ${vars[1]}] of ${tempVar}) {\n`;
498
+ p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const [${vars[0]}, ${vars[1]}] of ${tempVar}) {\n`);
433
499
  } else {
434
- code += `${this.i()}for (const ${vars[0]} of ${tempVar}) {\n`;
500
+ p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const ${vars[0]} of ${tempVar}) {\n`);
435
501
  }
436
502
  this.indent++;
437
- code += `${this.i()}${enteredVar} = true;\n`;
438
- code += this.genBlockStatements(node.body);
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
- code += `\n${this.i()}}\n`;
509
+ p.push(`\n${this.i()}}\n`);
441
510
  this.popScope();
442
511
  this.pushScope();
443
- code += `${this.i()}if (!${enteredVar}) {\n`;
512
+ p.push(`${this.i()}if (!${enteredVar}) {\n`);
444
513
  this.indent++;
445
- code += this.genBlockStatements(node.elseBody);
514
+ p.push(this.genBlockStatements(node.elseBody));
446
515
  this.indent--;
447
- code += `\n${this.i()}}\n`;
516
+ p.push(`\n${this.i()}}\n`);
448
517
  this.popScope();
449
518
  this.indent--;
450
- code += `${this.i()}}`;
451
- return code;
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
- let code;
525
+ const p = [];
457
526
  if (vars.length === 2) {
458
- code = `${this.i()}for (const [${vars[0]}, ${vars[1]}] of ${iterExpr}) {\n`;
527
+ p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const [${vars[0]}, ${vars[1]}] of ${iterExpr}) {\n`);
459
528
  } else {
460
- code = `${this.i()}for (const ${vars[0]} of ${iterExpr}) {\n`;
529
+ p.push(`${this.i()}${labelPrefix}for${awaitKeyword} (const ${vars[0]} of ${iterExpr}) {\n`);
461
530
  }
462
531
  this.indent++;
463
- code += this.genBlockStatements(node.body);
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
- code += `\n${this.i()}}`;
537
+ p.push(`\n${this.i()}}`);
466
538
  this.popScope();
467
539
 
468
- return code;
540
+ return p.join('');
469
541
  }
470
542
 
471
543
  genWhileStatement(node) {
472
- let code = `${this.i()}while (${this.genExpression(node.condition)}) {\n`;
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
- code += this.genBlockStatements(node.body);
549
+ p.push(this.genBlockStatements(node.body));
476
550
  this.popScope();
477
551
  this.indent--;
478
- code += `\n${this.i()}}`;
479
- return code;
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
- let code = `${this.i()}try {\n`;
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
- code += this.generateStatement(stmt) + '\n';
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
- code += `${this.i()}} catch (${catchVar}) {\n`;
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
- code += `${this.i()}if (${catchVar} && ${catchVar}.__tova_propagate) throw ${catchVar};\n`;
587
+ p.push(`${this.i()}if (${catchVar} && ${catchVar}.__tova_propagate) throw ${catchVar};\n`);
500
588
  for (const stmt of node.catchBody) {
501
- code += this.generateStatement(stmt) + '\n';
589
+ p.push(this.generateStatement(stmt) + '\n');
502
590
  }
503
591
  this.popScope();
504
592
  this.indent--;
505
- code += `${this.i()}}`;
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
- code += `${this.i()}}`;
599
+ p.push(`${this.i()}}`);
512
600
  }
513
- code += ` finally {\n`;
601
+ p.push(` finally {\n`);
514
602
  this.indent++;
515
603
  this.pushScope();
516
604
  for (const stmt of node.finallyBody) {
517
- code += this.generateStatement(stmt) + '\n';
605
+ p.push(this.generateStatement(stmt) + '\n');
518
606
  }
519
607
  this.popScope();
520
608
  this.indent--;
521
- code += `${this.i()}}`;
609
+ p.push(`${this.i()}}`);
522
610
  }
523
611
 
524
- return code;
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
- let code = `{\n`;
627
+ const p = [];
628
+ p.push(`{\n`);
540
629
  this.indent++;
541
630
  this.pushScope();
542
- code += this.genBlockStatements(node);
631
+ p.push(this.genBlockStatements(node));
543
632
  this.popScope();
544
633
  this.indent--;
545
- code += `\n${this.i()}}`;
546
- return code;
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
- let code = `${this.i()}if (${this.genExpression(node.condition)}) {\n`;
618
- code += this._genBlockBodyReturns(node.consequent);
619
- code += `\n${this.i()}}`;
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
- code += ` else if (${this.genExpression(alt.condition)}) {\n`;
623
- code += this._genBlockBodyReturns(alt.body);
624
- code += `\n${this.i()}}`;
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
- code += ` else {\n`;
629
- code += this._genBlockBodyReturns(node.elseBody);
630
- code += `\n${this.i()}}`;
718
+ p.push(` else {\n`);
719
+ p.push(this._genBlockBodyReturns(node.elseBody));
720
+ p.push(`\n${this.i()}}`);
631
721
  }
632
722
 
633
- return code;
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._usedBuiltins.add(node.callee.name);
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: evaluate left once via IIFE temp var
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
- return `${asyncPrefix}(${params}) => {\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}}`;
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}) => {\n${body}\n${this.i()}}`;
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
- let code = `((${tempVar}) => {\n`;
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
- code += `${this.i()}const ${arm.pattern.name} = ${tempVar};\n`;
1545
+ p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
1269
1546
  }
1270
1547
  if (arm.body.type === 'BlockStatement') {
1271
- code += this.genBlockBody(arm.body) + '\n';
1548
+ p.push(this.genBlockBody(arm.body) + '\n');
1272
1549
  } else {
1273
- code += `${this.i()}return ${this.genExpression(arm.body)};\n`;
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
- code += `${this.i()}${keyword} (${condition}) {\n`;
1557
+ p.push(`${this.i()}${keyword} (${condition}) {\n`);
1281
1558
  this.indent++;
1282
1559
 
1283
1560
  // Bind variables from pattern
1284
- code += this.genPatternBindings(arm.pattern, tempVar);
1561
+ p.push(this.genPatternBindings(arm.pattern, tempVar));
1285
1562
 
1286
1563
  if (arm.body.type === 'BlockStatement') {
1287
- code += this.genBlockBody(arm.body) + '\n';
1564
+ p.push(this.genBlockBody(arm.body) + '\n');
1288
1565
  } else {
1289
- code += `${this.i()}return ${this.genExpression(arm.body)};\n`;
1566
+ p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
1290
1567
  }
1291
1568
  this.indent--;
1292
- code += `${this.i()}}\n`;
1569
+ p.push(`${this.i()}}\n`);
1293
1570
  }
1294
1571
 
1295
1572
  this.indent--;
1296
- code += `${this.i()}})(${subject})`;
1297
- return code;
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
- let code = `(() => {\n`;
1602
+ const p = [];
1603
+ p.push(`(() => {\n`);
1314
1604
  this.indent++;
1315
1605
 
1316
- code += `${this.i()}if (${this.genExpression(node.condition)}) {\n`;
1317
- code += this.genBlockBody(node.consequent);
1318
- code += `\n${this.i()}}`;
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
- code += ` else if (${this.genExpression(alt.condition)}) {\n`;
1322
- code += this.genBlockBody(alt.body);
1323
- code += `\n${this.i()}}`;
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
- code += ` else {\n`;
1327
- code += this.genBlockBody(node.elseBody);
1328
- code += `\n${this.i()}}`;
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
- code += `\n${this.i()}})()`;
1332
- return code;
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
- let code = `${this.i()}if (!(${this.genExpression(node.condition)})) {\n`;
1839
+ const p = [];
1840
+ p.push(`${this.i()}if (!(${this.genExpression(node.condition)})) {\n`);
1550
1841
  this.indent++;
1551
1842
  this.pushScope();
1552
- code += this.genBlockStatements(node.elseBody);
1843
+ p.push(this.genBlockStatements(node.elseBody));
1553
1844
  this.popScope();
1554
1845
  this.indent--;
1555
- code += `\n${this.i()}}`;
1556
- return code;
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 typeStr = node.typeExpr.name || 'any';
1696
- return `${this.i()}/* ${exportStr}type alias: ${node.name} = ${typeStr} */`;
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;