tova 0.5.1 → 0.8.2

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.
Files changed (60) hide show
  1. package/bin/tova.js +261 -60
  2. package/package.json +1 -1
  3. package/src/analyzer/analyzer.js +351 -11
  4. package/src/analyzer/{client-analyzer.js → browser-analyzer.js} +20 -17
  5. package/src/analyzer/deploy-analyzer.js +44 -0
  6. package/src/analyzer/form-analyzer.js +113 -0
  7. package/src/analyzer/scope.js +2 -2
  8. package/src/codegen/base-codegen.js +1160 -10
  9. package/src/codegen/{client-codegen.js → browser-codegen.js} +444 -5
  10. package/src/codegen/codegen.js +119 -28
  11. package/src/codegen/deploy-codegen.js +49 -0
  12. package/src/codegen/edge-codegen.js +1351 -0
  13. package/src/codegen/form-codegen.js +553 -0
  14. package/src/codegen/security-codegen.js +5 -5
  15. package/src/codegen/server-codegen.js +88 -7
  16. package/src/codegen/shared-codegen.js +5 -0
  17. package/src/codegen/wasm-codegen.js +6 -0
  18. package/src/config/edit-toml.js +6 -2
  19. package/src/config/git-resolver.js +128 -0
  20. package/src/config/lock-file.js +57 -0
  21. package/src/config/module-cache.js +58 -0
  22. package/src/config/module-entry.js +37 -0
  23. package/src/config/module-path.js +31 -0
  24. package/src/config/pkg-errors.js +62 -0
  25. package/src/config/resolve.js +17 -0
  26. package/src/config/resolver.js +139 -0
  27. package/src/config/search.js +28 -0
  28. package/src/config/semver.js +72 -0
  29. package/src/config/toml.js +48 -5
  30. package/src/deploy/deploy.js +217 -0
  31. package/src/deploy/infer.js +218 -0
  32. package/src/deploy/provision.js +311 -0
  33. package/src/diagnostics/error-codes.js +1 -1
  34. package/src/docs/generator.js +1 -1
  35. package/src/formatter/formatter.js +4 -4
  36. package/src/lexer/tokens.js +12 -2
  37. package/src/lsp/server.js +483 -1
  38. package/src/parser/ast.js +60 -5
  39. package/src/parser/{client-ast.js → browser-ast.js} +3 -3
  40. package/src/parser/{client-parser.js → browser-parser.js} +42 -15
  41. package/src/parser/concurrency-ast.js +15 -0
  42. package/src/parser/concurrency-parser.js +236 -0
  43. package/src/parser/deploy-ast.js +37 -0
  44. package/src/parser/deploy-parser.js +132 -0
  45. package/src/parser/edge-ast.js +83 -0
  46. package/src/parser/edge-parser.js +262 -0
  47. package/src/parser/form-ast.js +80 -0
  48. package/src/parser/form-parser.js +206 -0
  49. package/src/parser/parser.js +82 -14
  50. package/src/parser/select-ast.js +39 -0
  51. package/src/registry/plugins/browser-plugin.js +30 -0
  52. package/src/registry/plugins/concurrency-plugin.js +32 -0
  53. package/src/registry/plugins/deploy-plugin.js +33 -0
  54. package/src/registry/plugins/edge-plugin.js +32 -0
  55. package/src/registry/register-all.js +8 -2
  56. package/src/runtime/ssr.js +2 -2
  57. package/src/stdlib/inline.js +38 -6
  58. package/src/stdlib/runtime-bridge.js +152 -0
  59. package/src/version.js +1 -1
  60. package/src/registry/plugins/client-plugin.js +0 -30
@@ -1,7 +1,7 @@
1
1
  // Base code generation utilities shared across all codegen targets
2
2
  import { RESULT_OPTION, PROPAGATE, BUILTIN_NAMES, STDLIB_DEPS } from '../stdlib/inline.js';
3
3
  import { PIPE_TARGET } from '../parser/ast.js';
4
- import { compileWasmFunction, compileWasmModule, generateWasmGlue, generateMultiWasmGlue } from './wasm-codegen.js';
4
+ import { compileWasmFunction, compileWasmModule, generateWasmGlue, generateMultiWasmGlue, generateWasmBytesExport } from './wasm-codegen.js';
5
5
 
6
6
  export class BaseCodegen {
7
7
  constructor() {
@@ -25,6 +25,9 @@ export class BaseCodegen {
25
25
  this._sourceMappings = []; // {sourceLine, sourceCol, outputLine, outputCol, sourceFile?}
26
26
  this._outputLineCount = 0;
27
27
  this._sourceFile = null; // current source file for multi-file source maps
28
+ // @wasm function tracking for concurrent block WASM routing
29
+ this._wasmFunctions = new Map(); // funcName -> { node, wasmBytes }
30
+ this._needsRuntimeBridge = false; // set true when concurrent block uses WASM tasks
28
31
  // @fast mode for TypedArray optimization
29
32
  this._fastMode = false;
30
33
  this._typedArrayParams = new Map(); // paramName -> 'Float64Array' | 'Int32Array' | 'Uint8Array'
@@ -279,6 +282,8 @@ export class BaseCodegen {
279
282
  case 'TypeAlias': result = this.genTypeAlias(node); break;
280
283
  case 'DeferStatement': result = this.genDeferStatement(node); break;
281
284
  case 'WithStatement': result = this.genWithStatement(node); break;
285
+ case 'ConcurrentBlock': result = this.genConcurrentBlock(node); break;
286
+ case 'SelectStatement': result = this.genSelectStatement(node); break;
282
287
  case 'ExternDeclaration': result = `${this.i()}// extern: ${node.name}`; break;
283
288
  // Config declarations handled at block level — emit nothing in statement context
284
289
  case 'AiConfigDeclaration': result = ''; break;
@@ -286,6 +291,7 @@ export class BaseCodegen {
286
291
  case 'SourceDeclaration': result = ''; break;
287
292
  case 'PipelineDeclaration': result = ''; break;
288
293
  case 'ValidateBlock': result = ''; break;
294
+ case 'FormDeclaration': result = ''; break;
289
295
  case 'RefreshPolicy': result = ''; break;
290
296
  case 'RefinementType': result = this.genRefinementType(node); break;
291
297
  default:
@@ -352,6 +358,7 @@ export class BaseCodegen {
352
358
  case 'ColumnExpression': return this.genColumnExpression(node);
353
359
  case 'ColumnAssignment': return this.genColumnAssignment(node);
354
360
  case 'NegatedColumnExpression': return `{ __exclude: ${JSON.stringify(node.name)} }`;
361
+ case 'SpawnExpression': return this.genSpawnExpression(node);
355
362
  default:
356
363
  throw new Error(`Codegen: unknown expression type '${node.type}'`);
357
364
  }
@@ -490,7 +497,9 @@ export class BaseCodegen {
490
497
  return `${this.i()}const { ${props} } = ${this.genExpression(node.value)};`;
491
498
  }
492
499
  if (node.pattern.type === 'ArrayPattern' || node.pattern.type === 'TuplePattern') {
493
- for (const e of node.pattern.elements) if (e) this.declareVar(e);
500
+ for (const e of node.pattern.elements) {
501
+ if (e) this.declareVar(e.startsWith('...') ? e.slice(3) : e);
502
+ }
494
503
  const els = node.pattern.elements.map(e => e || '').join(', ');
495
504
  return `${this.i()}const [${els}] = ${this.genExpression(node.value)};`;
496
505
  }
@@ -571,9 +580,14 @@ export class BaseCodegen {
571
580
  // Track as user-defined to suppress stdlib version
572
581
  if (BUILTIN_NAMES.has(node.name)) this._userDefinedNames.add(node.name);
573
582
  const wasmBytes = compileWasmFunction(node);
574
- const glue = generateWasmGlue(node, wasmBytes);
583
+ // Track this WASM function for concurrent block routing
584
+ this._wasmFunctions.set(node.name, { node, wasmBytes });
575
585
  const exportPrefix = node.isPublic ? 'export ' : '';
576
- return `${this.i()}${exportPrefix}${glue}`;
586
+ // Emit bytes constant first, then glue that references it (avoids duplication)
587
+ const bytesExport = generateWasmBytesExport(node.name, wasmBytes);
588
+ const name = node.name;
589
+ const glue = `const ${name} = new WebAssembly.Instance(new WebAssembly.Module(__wasm_bytes_${name})).exports.${name};`;
590
+ return `${this.i()}${bytesExport}\n${this.i()}${exportPrefix}${glue}`;
577
591
  } catch (e) {
578
592
  // Fall back to JS if WASM compilation fails
579
593
  console.error(`Warning: @wasm compilation failed for '${node.name}': ${e.message}. Falling back to JS.`);
@@ -980,13 +994,22 @@ export class BaseCodegen {
980
994
  }
981
995
  }
982
996
 
997
+ // Pre-scan for scalar-replaceable Result/Option variables
998
+ const scalarMap = this._preAnalyzeScalarResults(regularStmts);
999
+ const prevScalar = this._scalarReplacements;
1000
+ this._scalarReplacements = scalarMap.size > 0 ? new Map([...(prevScalar || new Map()), ...scalarMap]) : prevScalar;
1001
+
983
1002
  for (let idx = 0; idx < regularStmts.length; idx++) {
984
1003
  if (bodySkipSet.has(idx)) continue;
985
1004
  const stmt = regularStmts[idx];
986
1005
  const isLast = idx === regularStmts.length - 1;
1006
+ // Scalar replacement: emit boolean+value pair instead of Result/Option allocation
1007
+ if (stmt.type === 'Assignment' && stmt.targets.length === 1 && typeof stmt.targets[0] === 'string' &&
1008
+ scalarMap.has(stmt.targets[0]) && scalarMap.get(stmt.targets[0]).assignIdx === idx) {
1009
+ lines.push(this._genScalarAssignment(stmt, scalarMap.get(stmt.targets[0])));
987
1010
  // Implicit return: last expression in function body
988
1011
  // Skip implicit return for known void/side-effect-only calls (print, assert, etc.)
989
- if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
1012
+ } else if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
990
1013
  // IIFE elimination: match/if as last expression in function body → direct returns
991
1014
  const expr = stmt.expression;
992
1015
  if (expr.type === 'MatchExpression' && !this._isSimpleMatch(expr)) {
@@ -1005,6 +1028,8 @@ export class BaseCodegen {
1005
1028
  }
1006
1029
  }
1007
1030
 
1031
+ this._scalarReplacements = prevScalar;
1032
+
1008
1033
  if (deferBodies.length > 0) {
1009
1034
  this.indent--;
1010
1035
  lines.push(`${this.i()}} finally {`);
@@ -1129,13 +1154,24 @@ export class BaseCodegen {
1129
1154
  lines.push(fillResult);
1130
1155
  }
1131
1156
  }
1157
+ // Pre-scan for scalar-replaceable Result/Option variables
1158
+ const scalarMap = this._preAnalyzeScalarResults(stmts);
1159
+ const prevScalar = this._scalarReplacements;
1160
+ this._scalarReplacements = scalarMap.size > 0 ? new Map([...(prevScalar || new Map()), ...scalarMap]) : prevScalar;
1132
1161
  for (let i = 0; i < stmts.length; i++) {
1133
1162
  if (skipSet.has(i)) continue;
1134
1163
  const s = stmts[i];
1135
- lines.push(this.generateStatement(s));
1164
+ // Scalar replacement: emit boolean+value pair instead of Result/Option allocation
1165
+ if (s.type === 'Assignment' && s.targets.length === 1 && typeof s.targets[0] === 'string' &&
1166
+ scalarMap.has(s.targets[0]) && scalarMap.get(s.targets[0]).assignIdx === i) {
1167
+ lines.push(this._genScalarAssignment(s, scalarMap.get(s.targets[0])));
1168
+ } else {
1169
+ lines.push(this.generateStatement(s));
1170
+ }
1136
1171
  // Dead code elimination: stop after unconditional return/break/continue
1137
1172
  if (s.type === 'ReturnStatement' || s.type === 'BreakStatement' || s.type === 'ContinueStatement') break;
1138
1173
  }
1174
+ this._scalarReplacements = prevScalar;
1139
1175
  return lines.join('\n');
1140
1176
  }
1141
1177
 
@@ -1432,6 +1468,41 @@ export class BaseCodegen {
1432
1468
  const fusedResult = this._tryFuseMapChain(node);
1433
1469
  if (fusedResult !== null) return fusedResult;
1434
1470
 
1471
+ // Compile-time devirtualization: Ok(x).unwrap() → x, Err(e).isOk() → false, etc.
1472
+ const devirt = this._tryDevirtualizeResultOption(node);
1473
+ if (devirt !== null) return devirt;
1474
+
1475
+ // Scalar replacement: intercept method calls on scalar-replaced Result/Option variables
1476
+ if (this._scalarReplacements && this._scalarReplacements.size > 0 &&
1477
+ node.callee.type === 'MemberExpression' && !node.callee.computed &&
1478
+ node.callee.object && node.callee.object.type === 'Identifier') {
1479
+ const varName = node.callee.object.name;
1480
+ const scalarInfo = this._scalarReplacements.get(varName);
1481
+ if (scalarInfo) {
1482
+ const okVar = `${varName}__ok`;
1483
+ const vVar = `${varName}__v`;
1484
+ const method = node.callee.property;
1485
+
1486
+ if (scalarInfo.kind === 'result') {
1487
+ switch (method) {
1488
+ case 'isOk': return okVar;
1489
+ case 'isErr': return `!${okVar}`;
1490
+ case 'unwrap': return vVar;
1491
+ case 'unwrapOr': return node.arguments.length > 0 ? `(${okVar} ? ${vVar} : ${this.genExpression(node.arguments[0])})` : vVar;
1492
+ case 'expect': return vVar;
1493
+ }
1494
+ } else if (scalarInfo.kind === 'option') {
1495
+ switch (method) {
1496
+ case 'isSome': return okVar;
1497
+ case 'isNone': return `!${okVar}`;
1498
+ case 'unwrap': return vVar;
1499
+ case 'unwrapOr': return node.arguments.length > 0 ? `(${okVar} ? ${vVar} : ${this.genExpression(node.arguments[0])})` : vVar;
1500
+ case 'expect': return vVar;
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+
1435
1506
  // Transform Foo.new(...) → new Foo(...)
1436
1507
  if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.property === 'new') {
1437
1508
  const obj = this.genExpression(node.callee.object);
@@ -1582,6 +1653,207 @@ export class BaseCodegen {
1582
1653
  return result;
1583
1654
  }
1584
1655
 
1656
+ // ─── Compile-time devirtualization for Result/Option ──────────
1657
+ // When the codegen sees Ok(val).method(), Err(val).method(), Some(val).method(),
1658
+ // or None.method(), it knows the exact type at compile time. Instead of allocating
1659
+ // a Result/Option object and calling a method, inline the method body directly.
1660
+
1661
+ _tryDevirtualizeResultOption(node) {
1662
+ // Must be a CallExpression where callee is a non-computed MemberExpression
1663
+ if (node.callee.type !== 'MemberExpression' || node.callee.computed) return null;
1664
+ const method = node.callee.property;
1665
+ const obj = node.callee.object;
1666
+ const args = node.arguments;
1667
+
1668
+ // Case 1: None.method()
1669
+ if (obj.type === 'Identifier' && obj.name === 'None') {
1670
+ return this._devirtNone(method, args);
1671
+ }
1672
+
1673
+ // Case 2: Ok(val).method(), Err(val).method(), Some(val).method()
1674
+ if (obj.type === 'CallExpression' && obj.callee.type === 'Identifier' && obj.arguments.length === 1) {
1675
+ const ctor = obj.callee.name;
1676
+ const val = obj.arguments[0];
1677
+ if (ctor === 'Ok') return this._devirtOk(val, method, args);
1678
+ if (ctor === 'Err') return this._devirtErr(val, method, args);
1679
+ if (ctor === 'Some') return this._devirtSome(val, method, args);
1680
+ }
1681
+
1682
+ return null;
1683
+ }
1684
+
1685
+ _devirtOk(val, method, args) {
1686
+ switch (method) {
1687
+ case 'unwrap':
1688
+ case 'expect':
1689
+ return this.genExpression(val);
1690
+ case 'unwrapOr':
1691
+ return this.genExpression(val); // Ok ignores default
1692
+ case 'isOk':
1693
+ return 'true';
1694
+ case 'isErr':
1695
+ return 'false';
1696
+ case 'map': {
1697
+ if (!this._isSimpleLambda(args[0])) return null;
1698
+ const parts = this._extractLambdaParts(args[0]);
1699
+ if (!parts) return null;
1700
+ this._needsResultOption = true;
1701
+ return `Ok(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1702
+ }
1703
+ case 'flatMap': {
1704
+ if (!this._isSimpleLambda(args[0])) return null;
1705
+ const parts = this._extractLambdaParts(args[0]);
1706
+ if (!parts) return null;
1707
+ return this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val));
1708
+ }
1709
+ case 'mapErr':
1710
+ this._needsResultOption = true;
1711
+ return `Ok(${this.genExpression(val)})`; // passes through
1712
+ case 'or':
1713
+ this._needsResultOption = true;
1714
+ return `Ok(${this.genExpression(val)})`; // passes through
1715
+ case 'and':
1716
+ if (args[0]) return this.genExpression(args[0]);
1717
+ return null;
1718
+ default:
1719
+ return null;
1720
+ }
1721
+ }
1722
+
1723
+ _devirtErr(val, method, args) {
1724
+ switch (method) {
1725
+ case 'unwrapOr':
1726
+ if (args[0]) return this.genExpression(args[0]);
1727
+ return null;
1728
+ case 'isOk':
1729
+ return 'false';
1730
+ case 'isErr':
1731
+ return 'true';
1732
+ case 'unwrapErr':
1733
+ return this.genExpression(val);
1734
+ case 'map':
1735
+ this._needsResultOption = true;
1736
+ return `Err(${this.genExpression(val)})`; // passes through
1737
+ case 'flatMap':
1738
+ this._needsResultOption = true;
1739
+ return `Err(${this.genExpression(val)})`;
1740
+ case 'mapErr': {
1741
+ if (!this._isSimpleLambda(args[0])) return null;
1742
+ const parts = this._extractLambdaParts(args[0]);
1743
+ if (!parts) return null;
1744
+ this._needsResultOption = true;
1745
+ return `Err(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1746
+ }
1747
+ case 'or':
1748
+ if (args[0]) return this.genExpression(args[0]);
1749
+ return null;
1750
+ case 'and':
1751
+ this._needsResultOption = true;
1752
+ return `Err(${this.genExpression(val)})`;
1753
+ default:
1754
+ return null;
1755
+ }
1756
+ }
1757
+
1758
+ _devirtSome(val, method, args) {
1759
+ switch (method) {
1760
+ case 'unwrap':
1761
+ case 'expect':
1762
+ return this.genExpression(val);
1763
+ case 'unwrapOr':
1764
+ return this.genExpression(val); // Some ignores default
1765
+ case 'isSome':
1766
+ return 'true';
1767
+ case 'isNone':
1768
+ return 'false';
1769
+ case 'map': {
1770
+ if (!this._isSimpleLambda(args[0])) return null;
1771
+ const parts = this._extractLambdaParts(args[0]);
1772
+ if (!parts) return null;
1773
+ this._needsResultOption = true;
1774
+ return `Some(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1775
+ }
1776
+ case 'flatMap': {
1777
+ if (!this._isSimpleLambda(args[0])) return null;
1778
+ const parts = this._extractLambdaParts(args[0]);
1779
+ if (!parts) return null;
1780
+ return this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val));
1781
+ }
1782
+ case 'filter': {
1783
+ if (!this._isSimpleLambda(args[0])) return null;
1784
+ const parts = this._extractLambdaParts(args[0]);
1785
+ if (!parts) return null;
1786
+ this._needsResultOption = true;
1787
+ const valCode = this.genExpression(val);
1788
+ const predCode = this._substituteParam(parts.bodyExpr, parts.paramName, valCode);
1789
+ return `(${predCode} ? Some(${valCode}) : None)`;
1790
+ }
1791
+ case 'or':
1792
+ this._needsResultOption = true;
1793
+ return `Some(${this.genExpression(val)})`; // passes through
1794
+ case 'and':
1795
+ if (args[0]) return this.genExpression(args[0]);
1796
+ return null;
1797
+ default:
1798
+ return null;
1799
+ }
1800
+ }
1801
+
1802
+ _devirtNone(method, args) {
1803
+ switch (method) {
1804
+ case 'unwrapOr':
1805
+ if (args[0]) return this.genExpression(args[0]);
1806
+ return null;
1807
+ case 'isSome':
1808
+ return 'false';
1809
+ case 'isNone':
1810
+ return 'true';
1811
+ case 'map':
1812
+ this._needsResultOption = true;
1813
+ return 'None';
1814
+ case 'flatMap':
1815
+ this._needsResultOption = true;
1816
+ return 'None';
1817
+ case 'filter':
1818
+ this._needsResultOption = true;
1819
+ return 'None';
1820
+ case 'or':
1821
+ if (args[0]) return this.genExpression(args[0]);
1822
+ return null;
1823
+ case 'and':
1824
+ this._needsResultOption = true;
1825
+ return 'None';
1826
+ default:
1827
+ return null;
1828
+ }
1829
+ }
1830
+
1831
+ _isSimpleLambda(node) {
1832
+ if (!node) return false;
1833
+ if (node.type !== 'FunctionExpression' && node.type !== 'ArrowFunction' && node.type !== 'LambdaExpression') return false;
1834
+ const params = node.params || [];
1835
+ if (params.length !== 1) return false;
1836
+ return true;
1837
+ }
1838
+
1839
+ _extractLambdaParts(lambda) {
1840
+ const params = lambda.params || [];
1841
+ const paramName = typeof params[0] === 'string' ? params[0] : (params[0].name || null);
1842
+ if (!paramName) return null;
1843
+ let bodyExpr = lambda.body;
1844
+ if (bodyExpr && bodyExpr.type === 'BlockStatement') {
1845
+ if (bodyExpr.body.length === 1) {
1846
+ const s = bodyExpr.body[0];
1847
+ if (s.type === 'ExpressionStatement') bodyExpr = s.expression;
1848
+ else if (s.type === 'ReturnStatement' && s.value) bodyExpr = s.value;
1849
+ else return null;
1850
+ } else {
1851
+ return null;
1852
+ }
1853
+ }
1854
+ return { paramName, bodyExpr };
1855
+ }
1856
+
1585
1857
  // Inline known builtins to direct method calls, eliminating wrapper overhead.
1586
1858
  // Returns the inlined code string, or null if not inlineable.
1587
1859
  _tryInlineBuiltin(node) {
@@ -2184,15 +2456,23 @@ export class BaseCodegen {
2184
2456
 
2185
2457
  if (arm.pattern.type === 'WildcardPattern' || arm.pattern.type === 'BindingPattern') {
2186
2458
  if (idx === node.arms.length - 1 && !arm.guard) {
2187
- // Default case
2459
+ // Default case — wrap in else block if preceded by if/else-if arms
2460
+ if (idx > 0) {
2461
+ p.push(`${this.i()}else {\n`);
2462
+ this.indent++;
2463
+ }
2188
2464
  if (arm.pattern.type === 'BindingPattern') {
2189
2465
  p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
2190
2466
  }
2191
2467
  if (arm.body.type === 'BlockStatement') {
2192
- p.push(this.genBlockBody(arm.body) + '\n');
2468
+ p.push(this._genBlockBodyReturns(arm.body) + '\n');
2193
2469
  } else {
2194
2470
  p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
2195
2471
  }
2472
+ if (idx > 0) {
2473
+ this.indent--;
2474
+ p.push(`${this.i()}}\n`);
2475
+ }
2196
2476
  break;
2197
2477
  }
2198
2478
  }
@@ -2205,7 +2485,7 @@ export class BaseCodegen {
2205
2485
  p.push(this.genPatternBindings(arm.pattern, tempVar));
2206
2486
 
2207
2487
  if (arm.body.type === 'BlockStatement') {
2208
- p.push(this.genBlockBody(arm.body) + '\n');
2488
+ p.push(this._genBlockBodyReturns(arm.body) + '\n');
2209
2489
  } else {
2210
2490
  p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
2211
2491
  }
@@ -2612,13 +2892,53 @@ export class BaseCodegen {
2612
2892
  if (pattern.type === 'BindingPattern') {
2613
2893
  cond = `((${pattern.name}) => ${this.genExpression(guard)})(${subject})`;
2614
2894
  } else {
2615
- cond = `(${cond}) && (${this.genExpression(guard)})`;
2895
+ // Collect bindings from sub-patterns (e.g. [1, n] if n < 2)
2896
+ const bindings = this._collectPatternBindings(pattern, subject);
2897
+ if (bindings.length > 0) {
2898
+ const params = bindings.map(b => b.name).join(', ');
2899
+ const args = bindings.map(b => b.accessor).join(', ');
2900
+ cond = `(${cond}) && ((${params}) => ${this.genExpression(guard)})(${args})`;
2901
+ } else {
2902
+ cond = `(${cond}) && (${this.genExpression(guard)})`;
2903
+ }
2616
2904
  }
2617
2905
  }
2618
2906
 
2619
2907
  return cond;
2620
2908
  }
2621
2909
 
2910
+ _collectPatternBindings(pattern, subject) {
2911
+ const bindings = [];
2912
+ switch (pattern.type) {
2913
+ case 'BindingPattern':
2914
+ bindings.push({ name: pattern.name, accessor: subject });
2915
+ break;
2916
+ case 'ArrayPattern':
2917
+ case 'TuplePattern':
2918
+ for (let i = 0; i < pattern.elements.length; i++) {
2919
+ const el = pattern.elements[i];
2920
+ if (el) bindings.push(...this._collectPatternBindings(el, `${subject}[${i}]`));
2921
+ }
2922
+ break;
2923
+ case 'VariantPattern': {
2924
+ const declaredFields = this._variantFields[pattern.name] || [];
2925
+ for (let i = 0; i < pattern.fields.length; i++) {
2926
+ const f = pattern.fields[i];
2927
+ const fieldName = typeof f === 'string' ? f : (f.type === 'BindingPattern' ? f.name : null);
2928
+ const propName = declaredFields[i] || fieldName || 'value';
2929
+ const accessor = `${subject}.${propName}`;
2930
+ if (typeof f === 'string') {
2931
+ bindings.push({ name: f, accessor });
2932
+ } else {
2933
+ bindings.push(...this._collectPatternBindings(f, accessor));
2934
+ }
2935
+ }
2936
+ break;
2937
+ }
2938
+ }
2939
+ return bindings;
2940
+ }
2941
+
2622
2942
  genPatternBindings(pattern, subject) {
2623
2943
  switch (pattern.type) {
2624
2944
  case 'BindingPattern':
@@ -3012,6 +3332,395 @@ export class BaseCodegen {
3012
3332
  return p.join('\n');
3013
3333
  }
3014
3334
 
3335
+ genConcurrentBlock(node) {
3336
+ const lines = [];
3337
+ const tasks = []; // { callCode, calleeName, spawn, isWasm }
3338
+ const assignments = [];
3339
+
3340
+ // Collect spawn tasks from body, preserving spawn node info
3341
+ for (const stmt of node.body) {
3342
+ if (stmt.type === 'Assignment' && stmt.values && stmt.values[0] && stmt.values[0].type === 'SpawnExpression') {
3343
+ const spawn = stmt.values[0];
3344
+ const calleeName = spawn.callee && spawn.callee.type === 'Identifier' ? spawn.callee.name : null;
3345
+ const callCode = `${this.genExpression(spawn.callee)}(${spawn.arguments.map(a => this.genExpression(a)).join(', ')})`;
3346
+ const isWasm = calleeName && this._wasmFunctions.has(calleeName);
3347
+ tasks.push({ callCode, calleeName, spawn, isWasm });
3348
+ assignments.push(stmt.targets[0]); // string variable name
3349
+ } else if (stmt.type === 'ExpressionStatement' && stmt.expression && stmt.expression.type === 'SpawnExpression') {
3350
+ const spawn = stmt.expression;
3351
+ const calleeName = spawn.callee && spawn.callee.type === 'Identifier' ? spawn.callee.name : null;
3352
+ const callCode = `${this.genExpression(spawn.callee)}(${spawn.arguments.map(a => this.genExpression(a)).join(', ')})`;
3353
+ const isWasm = calleeName && this._wasmFunctions.has(calleeName);
3354
+ tasks.push({ callCode, calleeName, spawn, isWasm });
3355
+ assignments.push(null); // fire-and-forget
3356
+ } else {
3357
+ // Non-spawn statement — generate normally
3358
+ lines.push(this.generateStatement(stmt));
3359
+ }
3360
+ }
3361
+
3362
+ this._needsResultOption = true;
3363
+
3364
+ if (tasks.length === 0) {
3365
+ return lines.join('\n');
3366
+ }
3367
+
3368
+ const base = this._concurrentCounter = (this._concurrentCounter || 0) + 1;
3369
+ const tempVars = tasks.map((_, i) => `__c${base}_${i}`);
3370
+
3371
+ // Classify tasks
3372
+ const wasmTasks = tasks.filter(t => t.isWasm);
3373
+ const allWasm = wasmTasks.length === tasks.length;
3374
+ const hasWasm = wasmTasks.length > 0;
3375
+
3376
+ // Check if all WASM tasks share the same function (shared module optimization)
3377
+ const allSameWasm = allWasm && wasmTasks.every(t => t.calleeName === wasmTasks[0].calleeName);
3378
+
3379
+ // --- WASM path: all tasks are @wasm functions ---
3380
+ if (allWasm && (node.mode === 'all' || node.mode === 'first' || node.mode === 'cancel_on_error' || (node.mode === 'timeout' && node.timeout))) {
3381
+ this._needsRuntimeBridge = true;
3382
+ const rtVar = `__tova_rt`;
3383
+ // Build task descriptors
3384
+ const taskDescs = tasks.map(t => {
3385
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3386
+ return `{ wasm: __wasm_bytes_${t.calleeName}, func: ${JSON.stringify(t.calleeName)}, args: [${argsCode}] }`;
3387
+ });
3388
+ const taskArrayCode = `[${taskDescs.join(', ')}]`;
3389
+
3390
+ // Choose runtime function based on mode
3391
+ let rtFunc;
3392
+ let rtCallSuffix = '';
3393
+ if (node.mode === 'first') {
3394
+ rtFunc = 'concurrentWasmFirst';
3395
+ } else if (node.mode === 'cancel_on_error') {
3396
+ rtFunc = 'concurrentWasmCancelOnError';
3397
+ } else if (node.mode === 'timeout' && node.timeout) {
3398
+ rtFunc = 'concurrentWasmTimeout';
3399
+ rtCallSuffix = `, ${this.genExpression(node.timeout)}`;
3400
+ } else {
3401
+ rtFunc = allSameWasm ? 'concurrentWasmShared' : 'concurrentWasm';
3402
+ }
3403
+
3404
+ if (node.mode === 'first') {
3405
+ // First mode: single result from WASM, fallback to Promise.race
3406
+ const firstVar = `__first${base}`;
3407
+ lines.push(`${this.i()}let ${firstVar};`);
3408
+ lines.push(`${this.i()}if (typeof ${rtVar} !== 'undefined' && ${rtVar} && ${rtVar}.isRuntimeAvailable()) {`);
3409
+ lines.push(`${this.i()} ${firstVar} = { __result: new Ok(await ${rtVar}.${rtFunc}(${taskArrayCode})) };`);
3410
+ lines.push(`${this.i()}} else {`);
3411
+ // Fallback: Promise.race
3412
+ const acVar = `__ac${base}`;
3413
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3414
+ const fallbackFirst = tasks.map((t, i) =>
3415
+ `(async () => { try { const __r = await ${t.callCode}; ${acVar}.abort(); return { __idx: ${i}, __result: new Ok(__r) }; } catch(__e) { return { __idx: ${i}, __result: new Err(__e) }; } })()`
3416
+ );
3417
+ lines.push(`${this.i()} ${firstVar} = await Promise.race([`);
3418
+ for (let i = 0; i < fallbackFirst.length; i++) {
3419
+ lines.push(`${this.i()} ${fallbackFirst[i]}${i < fallbackFirst.length - 1 ? ',' : ''}`);
3420
+ }
3421
+ lines.push(`${this.i()} ]);`);
3422
+ lines.push(`${this.i()}}`);
3423
+ // Assign winner to all named variables
3424
+ for (let i = 0; i < assignments.length; i++) {
3425
+ if (assignments[i]) {
3426
+ this.declareVar(assignments[i]);
3427
+ lines.push(`${this.i()}const ${assignments[i]} = ${firstVar}.__result;`);
3428
+ }
3429
+ }
3430
+ return lines.join('\n');
3431
+ }
3432
+
3433
+ // All other WASM modes: array result
3434
+ lines.push(`${this.i()}let ${tempVars.join(', ')};`);
3435
+ lines.push(`${this.i()}if (typeof ${rtVar} !== 'undefined' && ${rtVar} && ${rtVar}.isRuntimeAvailable()) {`);
3436
+ if (node.mode === 'cancel_on_error') {
3437
+ // cancel_on_error: WASM function throws on first task error — wrap with try/catch
3438
+ // to provide per-task Err results consistent with JS fallback path
3439
+ lines.push(`${this.i()} try {`);
3440
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode});`);
3441
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3442
+ lines.push(`${this.i()} } catch (__wasm_err) {`);
3443
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = Array(${tasks.length}).fill(new Err(__wasm_err));`);
3444
+ lines.push(`${this.i()} }`);
3445
+ } else if (node.mode === 'timeout' && node.timeout) {
3446
+ // timeout: WASM function throws on timeout — wrap with try/catch
3447
+ lines.push(`${this.i()} try {`);
3448
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode}${rtCallSuffix});`);
3449
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3450
+ lines.push(`${this.i()} } catch (__wasm_err) {`);
3451
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = Array(${tasks.length}).fill(new Err(__wasm_err));`);
3452
+ lines.push(`${this.i()} }`);
3453
+ } else {
3454
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode}${rtCallSuffix});`);
3455
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3456
+ }
3457
+ lines.push(`${this.i()}} else {`);
3458
+ // Fallback: standard Promise.all path
3459
+ if (node.mode === 'cancel_on_error') {
3460
+ const acVar = `__ac${base}`;
3461
+ const abortedVar = `__aborted${base}`;
3462
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3463
+ lines.push(`${this.i()} const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('cancelled')), { once: true }); });`);
3464
+ const fallbackExprs = tasks.map(t =>
3465
+ `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`
3466
+ );
3467
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3468
+ for (let i = 0; i < fallbackExprs.length; i++) {
3469
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3470
+ }
3471
+ lines.push(`${this.i()} ]);`);
3472
+ } else if (node.mode === 'timeout' && node.timeout) {
3473
+ const timeoutMs = this.genExpression(node.timeout);
3474
+ const acVar = `__ac${base}`;
3475
+ const abortedVar = `__aborted${base}`;
3476
+ const timerVar = `__timer${base}`;
3477
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3478
+ lines.push(`${this.i()} const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('concurrent timeout')), { once: true }); });`);
3479
+ lines.push(`${this.i()} const ${timerVar} = setTimeout(() => ${acVar}.abort(), ${timeoutMs});`);
3480
+ const fallbackExprs = tasks.map(t =>
3481
+ `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`
3482
+ );
3483
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3484
+ for (let i = 0; i < fallbackExprs.length; i++) {
3485
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3486
+ }
3487
+ lines.push(`${this.i()} ]);`);
3488
+ lines.push(`${this.i()} clearTimeout(${timerVar});`);
3489
+ } else {
3490
+ const fallbackExprs = tasks.map(t =>
3491
+ `(async () => { try { return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`
3492
+ );
3493
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3494
+ for (let i = 0; i < fallbackExprs.length; i++) {
3495
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3496
+ }
3497
+ lines.push(`${this.i()} ]);`);
3498
+ }
3499
+ lines.push(`${this.i()}}`);
3500
+ } else {
3501
+ // --- JS path (with per-task WASM routing for mixed blocks) ---
3502
+ const taskExprs = tasks.map(t => {
3503
+ if (t.isWasm && hasWasm) {
3504
+ // Route individual WASM task through runtime with fallback
3505
+ this._needsRuntimeBridge = true;
3506
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3507
+ return `(async () => { try { if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { return new Ok(await __tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}])); } return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`;
3508
+ }
3509
+ return `(async () => { try { return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`;
3510
+ });
3511
+
3512
+ if (node.mode === 'timeout' && node.timeout) {
3513
+ const timeoutMs = this.genExpression(node.timeout);
3514
+ const acVar = `__ac${base}`;
3515
+ const abortedVar = `__aborted${base}`;
3516
+ const timerVar = `__timer${base}`;
3517
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3518
+ lines.push(`${this.i()}const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('concurrent timeout')), { once: true }); });`);
3519
+ lines.push(`${this.i()}const ${timerVar} = setTimeout(() => ${acVar}.abort(), ${timeoutMs});`);
3520
+ const taskExprsTimeout = tasks.map(t => {
3521
+ if (t.isWasm && hasWasm) {
3522
+ this._needsRuntimeBridge = true;
3523
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3524
+ return `(async () => { try { if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { return new Ok(await Promise.race([__tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}]), ${abortedVar}])); } return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`;
3525
+ }
3526
+ return `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`;
3527
+ });
3528
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3529
+ for (let i = 0; i < taskExprsTimeout.length; i++) {
3530
+ lines.push(`${this.i()} ${taskExprsTimeout[i]}${i < taskExprsTimeout.length - 1 ? ',' : ''}`);
3531
+ }
3532
+ lines.push(`${this.i()}]);`);
3533
+ lines.push(`${this.i()}clearTimeout(${timerVar});`);
3534
+ } else if (node.mode === 'cancel_on_error') {
3535
+ const acVar = `__ac${base}`;
3536
+ const abortedVar = `__aborted${base}`;
3537
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3538
+ lines.push(`${this.i()}const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('cancelled')), { once: true }); });`);
3539
+ const taskExprsAbort = tasks.map(t => {
3540
+ if (t.isWasm && hasWasm) {
3541
+ this._needsRuntimeBridge = true;
3542
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3543
+ return `(async () => { try { if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { return new Ok(await Promise.race([__tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}]), ${abortedVar}])); } return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`;
3544
+ }
3545
+ return `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`;
3546
+ });
3547
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3548
+ for (let i = 0; i < taskExprsAbort.length; i++) {
3549
+ lines.push(`${this.i()} ${taskExprsAbort[i]}${i < taskExprsAbort.length - 1 ? ',' : ''}`);
3550
+ }
3551
+ lines.push(`${this.i()}]);`);
3552
+ } else if (node.mode === 'first') {
3553
+ const acVar = `__ac${base}`;
3554
+ const firstVar = `__first${base}`;
3555
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3556
+ const taskExprsFirst = tasks.map((t, i) => {
3557
+ if (t.isWasm && hasWasm) {
3558
+ this._needsRuntimeBridge = true;
3559
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3560
+ return `(async () => { try { let __r; if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { __r = await __tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}]); } else { __r = await ${t.callCode}; } ${acVar}.abort(); return { __idx: ${i}, __result: new Ok(__r) }; } catch(__e) { return { __idx: ${i}, __result: new Err(__e) }; } })()`;
3561
+ }
3562
+ return `(async () => { try { const __r = await ${t.callCode}; ${acVar}.abort(); return { __idx: ${i}, __result: new Ok(__r) }; } catch(__e) { return { __idx: ${i}, __result: new Err(__e) }; } })()`;
3563
+ });
3564
+ lines.push(`${this.i()}const ${firstVar} = await Promise.race([`);
3565
+ for (let i = 0; i < taskExprsFirst.length; i++) {
3566
+ lines.push(`${this.i()} ${taskExprsFirst[i]}${i < taskExprsFirst.length - 1 ? ',' : ''}`);
3567
+ }
3568
+ lines.push(`${this.i()}]);`);
3569
+ // For first mode, assign the winner's result to all named variables
3570
+ for (let i = 0; i < assignments.length; i++) {
3571
+ if (assignments[i]) {
3572
+ this.declareVar(assignments[i]);
3573
+ lines.push(`${this.i()}const ${assignments[i]} = ${firstVar}.__result;`);
3574
+ }
3575
+ }
3576
+ return lines.join('\n'); // early return — skip the normal assignment loop
3577
+ } else {
3578
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3579
+ for (let i = 0; i < taskExprs.length; i++) {
3580
+ lines.push(`${this.i()} ${taskExprs[i]}${i < taskExprs.length - 1 ? ',' : ''}`);
3581
+ }
3582
+ lines.push(`${this.i()}]);`);
3583
+ }
3584
+ }
3585
+
3586
+ // Assign results to named variables
3587
+ for (let i = 0; i < assignments.length; i++) {
3588
+ if (assignments[i]) {
3589
+ this.declareVar(assignments[i]);
3590
+ lines.push(`${this.i()}const ${assignments[i]} = ${tempVars[i]};`);
3591
+ }
3592
+ }
3593
+
3594
+ return lines.join('\n');
3595
+ }
3596
+
3597
+ genSelectStatement(node) {
3598
+ this._needsResultOption = true; // for Some/None in _tryReceive
3599
+ const base = this._selectCounter = (this._selectCounter || 0) + 1;
3600
+
3601
+ const hasDefault = node.cases.some(c => c.kind === 'default');
3602
+
3603
+ if (hasDefault) {
3604
+ return this._genSelectWithDefault(node, base);
3605
+ }
3606
+ return this._genSelectWithRace(node, base);
3607
+ }
3608
+
3609
+ _genSelectWithRace(node, base) {
3610
+ const lines = [];
3611
+ const selVar = `__sel${base}`;
3612
+ const promises = [];
3613
+
3614
+ for (let i = 0; i < node.cases.length; i++) {
3615
+ const c = node.cases[i];
3616
+ if (c.kind === 'receive') {
3617
+ const ch = this.genExpression(c.channel);
3618
+ promises.push(`${ch}.receive().then(__v => ({ __case: ${i}, __value: __v }))`);
3619
+ } else if (c.kind === 'send') {
3620
+ const ch = this.genExpression(c.channel);
3621
+ const val = this.genExpression(c.value);
3622
+ promises.push(`${ch}.send(${val}).then(() => ({ __case: ${i} }))`);
3623
+ } else if (c.kind === 'timeout') {
3624
+ const ms = this.genExpression(c.value);
3625
+ promises.push(`new Promise(__r => setTimeout(() => __r({ __case: ${i} }), ${ms}))`);
3626
+ }
3627
+ }
3628
+
3629
+ lines.push(`${this.i()}const ${selVar} = await Promise.race([`);
3630
+ for (let i = 0; i < promises.length; i++) {
3631
+ lines.push(`${this.i()} ${promises[i]}${i < promises.length - 1 ? ',' : ''}`);
3632
+ }
3633
+ lines.push(`${this.i()}]);`);
3634
+
3635
+ // Generate if/else chain (NOT switch — avoids break conflicts in loops)
3636
+ for (let i = 0; i < node.cases.length; i++) {
3637
+ const c = node.cases[i];
3638
+ const prefix = i === 0 ? 'if' : ' else if';
3639
+ lines.push(`${this.i()}${prefix} (${selVar}.__case === ${i}) {`);
3640
+ this.indent++;
3641
+ if (c.kind === 'receive' && c.binding) {
3642
+ this.declareVar(c.binding);
3643
+ lines.push(`${this.i()}const ${c.binding} = ${selVar}.__value.value;`);
3644
+ }
3645
+ for (const stmt of c.body) {
3646
+ lines.push(this.generateStatement(stmt));
3647
+ }
3648
+ this.indent--;
3649
+ lines.push(`${this.i()}}`);
3650
+ }
3651
+
3652
+ return lines.join('\n');
3653
+ }
3654
+
3655
+ _genSelectWithDefault(node, base) {
3656
+ const lines = [];
3657
+ const nonDefault = node.cases.filter(c => c.kind !== 'default');
3658
+ const defaultCase = node.cases.find(c => c.kind === 'default');
3659
+
3660
+ lines.push(`${this.i()}{`);
3661
+ this.indent++;
3662
+
3663
+ let depth = 0;
3664
+ for (let i = 0; i < nonDefault.length; i++) {
3665
+ const c = nonDefault[i];
3666
+ const tryVar = `__try${base}_${i}`;
3667
+
3668
+ if (c.kind === 'receive') {
3669
+ const ch = this.genExpression(c.channel);
3670
+ lines.push(`${this.i()}const ${tryVar} = ${ch}._tryReceive();`);
3671
+ lines.push(`${this.i()}if (${tryVar}.__tag === 'Some') {`);
3672
+ this.indent++;
3673
+ if (c.binding) {
3674
+ this.declareVar(c.binding);
3675
+ lines.push(`${this.i()}const ${c.binding} = ${tryVar}.value;`);
3676
+ }
3677
+ for (const stmt of c.body) {
3678
+ lines.push(this.generateStatement(stmt));
3679
+ }
3680
+ this.indent--;
3681
+ lines.push(`${this.i()}} else {`);
3682
+ this.indent++;
3683
+ depth++;
3684
+ } else if (c.kind === 'send') {
3685
+ const ch = this.genExpression(c.channel);
3686
+ const val = this.genExpression(c.value);
3687
+ lines.push(`${this.i()}const ${tryVar} = ${ch}._trySend(${val});`);
3688
+ lines.push(`${this.i()}if (${tryVar}) {`);
3689
+ this.indent++;
3690
+ for (const stmt of c.body) {
3691
+ lines.push(this.generateStatement(stmt));
3692
+ }
3693
+ this.indent--;
3694
+ lines.push(`${this.i()}} else {`);
3695
+ this.indent++;
3696
+ depth++;
3697
+ }
3698
+ }
3699
+
3700
+ // Default case body
3701
+ for (const stmt of defaultCase.body) {
3702
+ lines.push(this.generateStatement(stmt));
3703
+ }
3704
+
3705
+ // Close all else blocks
3706
+ for (let i = 0; i < depth; i++) {
3707
+ this.indent--;
3708
+ lines.push(`${this.i()}}`);
3709
+ }
3710
+
3711
+ this.indent--;
3712
+ lines.push(`${this.i()}}`);
3713
+
3714
+ return lines.join('\n');
3715
+ }
3716
+
3717
+ genSpawnExpression(node) {
3718
+ // Standalone spawn expression (not inside concurrent block codegen path)
3719
+ // This happens when spawn is used directly in an expression context
3720
+ const callCode = `${this.genExpression(node.callee)}(${node.arguments.map(a => this.genExpression(a)).join(', ')})`;
3721
+ return `(async () => { try { return new Ok(await ${callCode}); } catch(__e) { return new Err(__e); } })()`;
3722
+ }
3723
+
3015
3724
  // Check if a function body contains yield expressions (for generator detection)
3016
3725
  _containsYield(node) {
3017
3726
  if (!node) return false;
@@ -3035,4 +3744,445 @@ export class BaseCodegen {
3035
3744
  this._yieldCache.set(node, result);
3036
3745
  return result;
3037
3746
  }
3747
+
3748
+ // ─── Scalar replacement codegen for Result/Option ───────────
3749
+ // Generates boolean+value pairs instead of allocating Result/Option objects.
3750
+
3751
+ /**
3752
+ * Generates scalar-replaced code for an assignment like:
3753
+ * r = if cond { Ok(val) } else { Err(err) }
3754
+ * Output: let r__ok, r__v; if (cond) { r__ok = true; r__v = val; } else { ... }
3755
+ */
3756
+ _genScalarAssignment(node, info) {
3757
+ const varName = node.targets[0];
3758
+ const okVar = `${varName}__ok`;
3759
+ const vVar = `${varName}__v`;
3760
+ const ifExpr = node.values[0];
3761
+
3762
+ // Mark all three names as declared so later code doesn't redeclare
3763
+ this.declareVar(varName);
3764
+ this.declareVar(okVar);
3765
+ this.declareVar(vVar);
3766
+
3767
+ const parts = [];
3768
+ parts.push(`${this.i()}let ${okVar}, ${vVar};`);
3769
+
3770
+ // Build if/else chain
3771
+ let ifCode = `${this.i()}if (${this.genExpression(ifExpr.condition)}) {\n`;
3772
+ this.indent++;
3773
+ ifCode += this._genScalarBranch(ifExpr.consequent, okVar, vVar, info.kind);
3774
+ this.indent--;
3775
+ ifCode += `\n${this.i()}}`;
3776
+
3777
+ if (ifExpr.alternates) {
3778
+ for (const alt of ifExpr.alternates) {
3779
+ ifCode += ` else if (${this.genExpression(alt.condition)}) {\n`;
3780
+ this.indent++;
3781
+ ifCode += this._genScalarBranch(alt.body, okVar, vVar, info.kind);
3782
+ this.indent--;
3783
+ ifCode += `\n${this.i()}}`;
3784
+ }
3785
+ }
3786
+
3787
+ if (ifExpr.elseBody) {
3788
+ ifCode += ` else {\n`;
3789
+ this.indent++;
3790
+ ifCode += this._genScalarBranch(ifExpr.elseBody, okVar, vVar, info.kind);
3791
+ this.indent--;
3792
+ ifCode += `\n${this.i()}}`;
3793
+ }
3794
+
3795
+ parts.push(ifCode);
3796
+ return parts.join('\n');
3797
+ }
3798
+
3799
+ /**
3800
+ * Generates scalar assignments for one branch of an if expression.
3801
+ * Extracts the Ok/Err/Some/None constructor call from the last expression
3802
+ * and emits okVar = true/false; vVar = value;
3803
+ */
3804
+ _genScalarBranch(block, okVar, vVar, kind) {
3805
+ if (!block) return '';
3806
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
3807
+ const lines = [];
3808
+
3809
+ // Generate any statements before the last one (side effects)
3810
+ for (let i = 0; i < stmts.length - 1; i++) {
3811
+ lines.push(this.generateStatement(stmts[i]));
3812
+ }
3813
+
3814
+ // Last statement has the constructor
3815
+ const last = stmts[stmts.length - 1];
3816
+ let expr = last;
3817
+ if (last.type === 'ExpressionStatement') expr = last.expression;
3818
+ if (last.type === 'ReturnStatement' && last.value) expr = last.value;
3819
+
3820
+ if (expr.type === 'CallExpression' && expr.callee && expr.callee.type === 'Identifier') {
3821
+ const name = expr.callee.name;
3822
+ if (name === 'Ok' || name === 'Some') {
3823
+ lines.push(`${this.i()}${okVar} = true; ${vVar} = ${this.genExpression(expr.arguments[0])};`);
3824
+ } else if (name === 'Err') {
3825
+ lines.push(`${this.i()}${okVar} = false; ${vVar} = ${this.genExpression(expr.arguments[0])};`);
3826
+ }
3827
+ } else if (expr.type === 'Identifier' && expr.name === 'None') {
3828
+ lines.push(`${this.i()}${okVar} = false; ${vVar} = undefined;`);
3829
+ }
3830
+
3831
+ return lines.join('\n');
3832
+ }
3833
+
3834
+ // ─── Scalar replacement analysis for Result/Option ──────────
3835
+ // Detects local variables assigned from if-expressions that produce
3836
+ // Ok/Err or Some/None, where all subsequent usages are safe method
3837
+ // calls (isOk, unwrap, unwrapOr, etc.). Such variables can be
3838
+ // replaced with a boolean + value pair (zero allocation).
3839
+
3840
+ /**
3841
+ * Scans an array of statements for scalar-replaceable Result/Option
3842
+ * assignments. Returns Map<varName, {kind, assignIdx}>.
3843
+ */
3844
+ _preAnalyzeScalarResults(stmts) {
3845
+ const map = new Map();
3846
+ for (let i = 0; i < stmts.length; i++) {
3847
+ const stmt = stmts[i];
3848
+ // Must be a single-target, single-value assignment
3849
+ if (stmt.type !== 'Assignment' || stmt.targets.length !== 1 || stmt.values.length !== 1) continue;
3850
+ const target = stmt.targets[0];
3851
+ if (typeof target !== 'string') continue; // skip MemberExpression targets
3852
+ const value = stmt.values[0];
3853
+ // Value must be an IfExpression (or IfStatement used as expression)
3854
+ if (value.type !== 'IfExpression' && value.type !== 'IfStatement') continue;
3855
+ const kind = this._detectResultOptionIf(value);
3856
+ if (kind === null) continue;
3857
+ // Check all subsequent statements for safe usage
3858
+ let allSafe = true;
3859
+ let anyUsed = false;
3860
+ for (let j = i + 1; j < stmts.length; j++) {
3861
+ const usage = this._checkScalarSafeUsage(stmts[j], target, kind);
3862
+ if (usage === 'unsafe') { allSafe = false; break; }
3863
+ if (usage === 'used') anyUsed = true;
3864
+ }
3865
+ if (allSafe && anyUsed) {
3866
+ map.set(target, { kind, assignIdx: i });
3867
+ }
3868
+ }
3869
+ return map;
3870
+ }
3871
+
3872
+ /**
3873
+ * Checks if ALL branches of an if expression return Ok/Err or Some/None.
3874
+ * Returns 'result' | 'option' | null.
3875
+ */
3876
+ _detectResultOptionIf(ifExpr) {
3877
+ // Must have an else branch (otherwise result could be undefined)
3878
+ if (!ifExpr.elseBody) return null;
3879
+
3880
+ const types = [];
3881
+
3882
+ // Check consequent block
3883
+ types.push(this._getBlockResultType(ifExpr.consequent));
3884
+
3885
+ // Check elif alternates
3886
+ if (ifExpr.alternates) {
3887
+ for (const alt of ifExpr.alternates) {
3888
+ types.push(this._getBlockResultType(alt.body));
3889
+ }
3890
+ }
3891
+
3892
+ // Check else block
3893
+ types.push(this._getBlockResultType(ifExpr.elseBody));
3894
+
3895
+ // All must be non-null
3896
+ if (types.some(t => t === null)) return null;
3897
+
3898
+ // Check if all are Result constructors (Ok/Err)
3899
+ if (types.every(t => t === 'Ok' || t === 'Err')) return 'result';
3900
+
3901
+ // Check if all are Option constructors (Some/None)
3902
+ if (types.every(t => t === 'Some' || t === 'None')) return 'option';
3903
+
3904
+ return null;
3905
+ }
3906
+
3907
+ /**
3908
+ * Gets the constructor name from the last expression in a block.
3909
+ * Returns 'Ok' | 'Err' | 'Some' | 'None' | null.
3910
+ */
3911
+ _getBlockResultType(block) {
3912
+ if (!block) return null;
3913
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
3914
+ if (stmts.length === 0) return null;
3915
+ const last = stmts[stmts.length - 1];
3916
+ // Could be ExpressionStatement or bare expression (implicit return)
3917
+ let expr = last;
3918
+ if (last.type === 'ExpressionStatement') expr = last.expression;
3919
+ if (last.type === 'ReturnStatement' && last.value) expr = last.value;
3920
+ // Check for Ok(val), Err(val), Some(val) call
3921
+ if (expr.type === 'CallExpression' && expr.callee && expr.callee.type === 'Identifier') {
3922
+ const name = expr.callee.name;
3923
+ if (['Ok', 'Err', 'Some'].includes(name) && expr.arguments.length === 1) return name;
3924
+ }
3925
+ // Check for bare None identifier
3926
+ if (expr.type === 'Identifier' && expr.name === 'None') return 'None';
3927
+ return null;
3928
+ }
3929
+
3930
+ /**
3931
+ * Walks a statement checking all references to varName.
3932
+ * Returns 'used' | 'unsafe' | 'none'.
3933
+ */
3934
+ _checkScalarSafeUsage(stmt, varName, kind) {
3935
+ let found = false;
3936
+ let safe = true;
3937
+
3938
+ const SAFE_RESULT = new Set(['isOk', 'isErr', 'unwrap', 'unwrapOr', 'expect']);
3939
+ const SAFE_OPTION = new Set(['isSome', 'isNone', 'unwrap', 'unwrapOr', 'expect']);
3940
+ const safeSet = kind === 'result' ? SAFE_RESULT : SAFE_OPTION;
3941
+
3942
+ // Custom expression walker that handles method calls specially
3943
+ const walkExpr = (expr) => {
3944
+ if (!expr || !safe) return;
3945
+ // Check for varName.method() pattern FIRST
3946
+ if (expr.type === 'CallExpression' &&
3947
+ expr.callee && expr.callee.type === 'MemberExpression' &&
3948
+ !expr.callee.computed &&
3949
+ expr.callee.object && expr.callee.object.type === 'Identifier' &&
3950
+ expr.callee.object.name === varName) {
3951
+ if (safeSet.has(expr.callee.property)) {
3952
+ found = true;
3953
+ // Walk only the arguments (NOT the callee.object to avoid bare ref detection)
3954
+ for (const arg of expr.arguments) walkExpr(arg);
3955
+ return;
3956
+ }
3957
+ // Unsafe method call on the variable
3958
+ safe = false;
3959
+ return;
3960
+ }
3961
+ // Check for bare varName reference (not part of a safe method call)
3962
+ if (expr.type === 'Identifier' && expr.name === varName) {
3963
+ safe = false;
3964
+ return;
3965
+ }
3966
+ // Walk children
3967
+ switch (expr.type) {
3968
+ case 'BinaryExpression':
3969
+ case 'LogicalExpression':
3970
+ walkExpr(expr.left);
3971
+ walkExpr(expr.right);
3972
+ break;
3973
+ case 'UnaryExpression':
3974
+ walkExpr(expr.operand);
3975
+ break;
3976
+ case 'CallExpression':
3977
+ walkExpr(expr.callee);
3978
+ for (const arg of expr.arguments) walkExpr(arg);
3979
+ break;
3980
+ case 'MemberExpression':
3981
+ walkExpr(expr.object);
3982
+ if (expr.computed) walkExpr(expr.property);
3983
+ break;
3984
+ case 'ConditionalExpression':
3985
+ walkExpr(expr.condition);
3986
+ walkExpr(expr.consequent);
3987
+ walkExpr(expr.alternate);
3988
+ break;
3989
+ case 'ArrayLiteral':
3990
+ if (expr.elements) for (const el of expr.elements) walkExpr(el);
3991
+ break;
3992
+ case 'ObjectLiteral':
3993
+ if (expr.properties) for (const prop of expr.properties) walkExpr(prop.value);
3994
+ break;
3995
+ case 'TemplateLiteral':
3996
+ if (expr.expressions) for (const e of expr.expressions) walkExpr(e);
3997
+ break;
3998
+ // Lambdas/functions — don't descend (separate scope)
3999
+ case 'FunctionExpression':
4000
+ case 'ArrowFunction':
4001
+ case 'LambdaExpression':
4002
+ break;
4003
+ // Assignment expression
4004
+ case 'AssignmentExpression':
4005
+ walkExpr(expr.right);
4006
+ break;
4007
+ }
4008
+ };
4009
+
4010
+ // Walk statement dispatching to expression walker
4011
+ this._walkStmtForScalar(stmt, walkExpr);
4012
+
4013
+ if (!safe) return 'unsafe';
4014
+ return found ? 'used' : 'none';
4015
+ }
4016
+
4017
+ /**
4018
+ * Walks all expressions within a statement, calling walkExpr for each
4019
+ * top-level expression found. Used by _checkScalarSafeUsage.
4020
+ */
4021
+ _walkStmtForScalar(stmt, walkExpr) {
4022
+ if (!stmt) return;
4023
+ switch (stmt.type) {
4024
+ case 'ExpressionStatement':
4025
+ walkExpr(stmt.expression);
4026
+ break;
4027
+ case 'Assignment':
4028
+ case 'VarDeclaration':
4029
+ if (stmt.values) for (const v of stmt.values) walkExpr(v);
4030
+ if (stmt.targets) {
4031
+ for (const t of stmt.targets) {
4032
+ if (typeof t === 'object') walkExpr(t);
4033
+ }
4034
+ }
4035
+ break;
4036
+ case 'ReturnStatement':
4037
+ if (stmt.value) walkExpr(stmt.value);
4038
+ break;
4039
+ case 'IfStatement':
4040
+ case 'IfExpression':
4041
+ walkExpr(stmt.condition);
4042
+ this._walkBlockForScalar(stmt.consequent, walkExpr);
4043
+ if (stmt.alternates) {
4044
+ for (const alt of stmt.alternates) {
4045
+ walkExpr(alt.condition);
4046
+ this._walkBlockForScalar(alt.body, walkExpr);
4047
+ }
4048
+ }
4049
+ if (stmt.elseBody) this._walkBlockForScalar(stmt.elseBody, walkExpr);
4050
+ break;
4051
+ case 'ForStatement':
4052
+ walkExpr(stmt.iterable);
4053
+ this._walkBlockForScalar(stmt.body, walkExpr);
4054
+ break;
4055
+ case 'WhileStatement':
4056
+ walkExpr(stmt.condition);
4057
+ this._walkBlockForScalar(stmt.body, walkExpr);
4058
+ break;
4059
+ case 'CallExpression':
4060
+ walkExpr(stmt);
4061
+ break;
4062
+ default:
4063
+ if (stmt.expression) walkExpr(stmt.expression);
4064
+ break;
4065
+ }
4066
+ }
4067
+
4068
+ /**
4069
+ * Walks all statements in a block, dispatching to _walkStmtForScalar.
4070
+ */
4071
+ _walkBlockForScalar(block, walkExpr) {
4072
+ if (!block) return;
4073
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4074
+ for (const s of stmts) this._walkStmtForScalar(s, walkExpr);
4075
+ }
4076
+
4077
+ /**
4078
+ * General-purpose expression walker. Calls callback(node) for every
4079
+ * expression node in the tree.
4080
+ */
4081
+ _walkExpressions(node, callback) {
4082
+ if (!node) return;
4083
+ callback(node);
4084
+ switch (node.type) {
4085
+ case 'BinaryExpression':
4086
+ case 'LogicalExpression':
4087
+ this._walkExpressions(node.left, callback);
4088
+ this._walkExpressions(node.right, callback);
4089
+ break;
4090
+ case 'UnaryExpression':
4091
+ this._walkExpressions(node.operand, callback);
4092
+ break;
4093
+ case 'CallExpression':
4094
+ this._walkExpressions(node.callee, callback);
4095
+ for (const arg of node.arguments) this._walkExpressions(arg, callback);
4096
+ break;
4097
+ case 'MemberExpression':
4098
+ this._walkExpressions(node.object, callback);
4099
+ if (node.computed) this._walkExpressions(node.property, callback);
4100
+ break;
4101
+ case 'ConditionalExpression':
4102
+ this._walkExpressions(node.condition, callback);
4103
+ this._walkExpressions(node.consequent, callback);
4104
+ this._walkExpressions(node.alternate, callback);
4105
+ break;
4106
+ case 'ArrayLiteral':
4107
+ if (node.elements) for (const el of node.elements) this._walkExpressions(el, callback);
4108
+ break;
4109
+ case 'ObjectLiteral':
4110
+ if (node.properties) for (const prop of node.properties) this._walkExpressions(prop.value, callback);
4111
+ break;
4112
+ case 'TemplateLiteral':
4113
+ if (node.expressions) for (const expr of node.expressions) this._walkExpressions(expr, callback);
4114
+ break;
4115
+ // Lambdas/functions — don't descend (separate scope)
4116
+ case 'FunctionExpression':
4117
+ case 'ArrowFunction':
4118
+ case 'LambdaExpression':
4119
+ break;
4120
+ // Leaf nodes — no children
4121
+ case 'Identifier':
4122
+ case 'NumberLiteral':
4123
+ case 'StringLiteral':
4124
+ case 'BooleanLiteral':
4125
+ case 'NilLiteral':
4126
+ break;
4127
+ }
4128
+ }
4129
+
4130
+ /**
4131
+ * Walks all expressions within a statement.
4132
+ */
4133
+ _walkStatementExpressions(stmt, callback) {
4134
+ if (!stmt) return;
4135
+ switch (stmt.type) {
4136
+ case 'ExpressionStatement':
4137
+ this._walkExpressions(stmt.expression, callback);
4138
+ break;
4139
+ case 'Assignment':
4140
+ case 'VarDeclaration':
4141
+ if (stmt.values) for (const v of stmt.values) this._walkExpressions(v, callback);
4142
+ if (stmt.targets) {
4143
+ for (const t of stmt.targets) {
4144
+ if (typeof t === 'object') this._walkExpressions(t, callback);
4145
+ }
4146
+ }
4147
+ break;
4148
+ case 'ReturnStatement':
4149
+ if (stmt.value) this._walkExpressions(stmt.value, callback);
4150
+ break;
4151
+ case 'IfStatement':
4152
+ case 'IfExpression':
4153
+ this._walkExpressions(stmt.condition, callback);
4154
+ this._walkStatementBlock(stmt.consequent, callback);
4155
+ if (stmt.alternates) {
4156
+ for (const alt of stmt.alternates) {
4157
+ this._walkExpressions(alt.condition, callback);
4158
+ this._walkStatementBlock(alt.body, callback);
4159
+ }
4160
+ }
4161
+ if (stmt.elseBody) this._walkStatementBlock(stmt.elseBody, callback);
4162
+ break;
4163
+ case 'ForStatement':
4164
+ this._walkExpressions(stmt.iterable, callback);
4165
+ this._walkStatementBlock(stmt.body, callback);
4166
+ break;
4167
+ case 'WhileStatement':
4168
+ this._walkExpressions(stmt.condition, callback);
4169
+ this._walkStatementBlock(stmt.body, callback);
4170
+ break;
4171
+ case 'CallExpression':
4172
+ this._walkExpressions(stmt, callback);
4173
+ break;
4174
+ default:
4175
+ if (stmt.expression) this._walkExpressions(stmt.expression, callback);
4176
+ break;
4177
+ }
4178
+ }
4179
+
4180
+ /**
4181
+ * Walks all statements in a block for _walkStatementExpressions.
4182
+ */
4183
+ _walkStatementBlock(block, callback) {
4184
+ if (!block) return;
4185
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4186
+ for (const s of stmts) this._walkStatementExpressions(s, callback);
4187
+ }
3038
4188
  }