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.
@@ -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) {
@@ -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._usedBuiltins.add(node.callee.name);
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: 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
+ }
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
- 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');
1227
1447
  }
1228
- return `${asyncPrefix}(${params}) => {\n${body}\n${this.i()}}`;
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
- let code = `((${tempVar}) => {\n`;
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
- code += `${this.i()}const ${arm.pattern.name} = ${tempVar};\n`;
1545
+ p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
1265
1546
  }
1266
1547
  if (arm.body.type === 'BlockStatement') {
1267
- code += this.genBlockBody(arm.body) + '\n';
1548
+ p.push(this.genBlockBody(arm.body) + '\n');
1268
1549
  } else {
1269
- code += `${this.i()}return ${this.genExpression(arm.body)};\n`;
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
- code += `${this.i()}${keyword} (${condition}) {\n`;
1557
+ p.push(`${this.i()}${keyword} (${condition}) {\n`);
1277
1558
  this.indent++;
1278
1559
 
1279
1560
  // Bind variables from pattern
1280
- code += this.genPatternBindings(arm.pattern, tempVar);
1561
+ p.push(this.genPatternBindings(arm.pattern, tempVar));
1281
1562
 
1282
1563
  if (arm.body.type === 'BlockStatement') {
1283
- code += this.genBlockBody(arm.body) + '\n';
1564
+ p.push(this.genBlockBody(arm.body) + '\n');
1284
1565
  } else {
1285
- code += `${this.i()}return ${this.genExpression(arm.body)};\n`;
1566
+ p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
1286
1567
  }
1287
1568
  this.indent--;
1288
- code += `${this.i()}}\n`;
1569
+ p.push(`${this.i()}}\n`);
1289
1570
  }
1290
1571
 
1291
1572
  this.indent--;
1292
- code += `${this.i()}})(${subject})`;
1293
- return code;
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
- let code = `(() => {\n`;
1602
+ const p = [];
1603
+ p.push(`(() => {\n`);
1310
1604
  this.indent++;
1311
1605
 
1312
- code += `${this.i()}if (${this.genExpression(node.condition)}) {\n`;
1313
- code += this.genBlockBody(node.consequent);
1314
- 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()}}`);
1315
1609
 
1316
1610
  for (const alt of node.alternates) {
1317
- code += ` else if (${this.genExpression(alt.condition)}) {\n`;
1318
- code += this.genBlockBody(alt.body);
1319
- 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()}}`);
1320
1614
  }
1321
1615
 
1322
- code += ` else {\n`;
1323
- code += this.genBlockBody(node.elseBody);
1324
- code += `\n${this.i()}}`;
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
- code += `\n${this.i()}})()`;
1328
- return code;
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
- let code = `${this.i()}if (!(${this.genExpression(node.condition)})) {\n`;
1839
+ const p = [];
1840
+ p.push(`${this.i()}if (!(${this.genExpression(node.condition)})) {\n`);
1546
1841
  this.indent++;
1547
1842
  this.pushScope();
1548
- code += this.genBlockStatements(node.elseBody);
1843
+ p.push(this.genBlockStatements(node.elseBody));
1549
1844
  this.popScope();
1550
1845
  this.indent--;
1551
- code += `\n${this.i()}}`;
1552
- return code;
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 typeStr = node.typeExpr.name || 'any';
1692
- 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';
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;