rip-lang 3.7.4 → 3.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/compiler.js CHANGED
@@ -148,6 +148,7 @@ export class CodeGenerator {
148
148
 
149
149
  // Special operators
150
150
  '%%': 'generateModulo',
151
+ '%%=': 'generateModuloAssign',
151
152
  '//': 'generateFloorDiv',
152
153
  '//=': 'generateFloorDivAssign',
153
154
  '..': 'generateRange',
@@ -201,9 +202,7 @@ export class CodeGenerator {
201
202
 
202
203
  // Control flow — simple
203
204
  'break': 'generateBreak',
204
- 'break-if': 'generateBreakIf',
205
205
  'continue': 'generateContinue',
206
- 'continue-if': 'generateContinueIf',
207
206
  '?': 'generateExistential',
208
207
  '?:': 'generateTernary',
209
208
  '|>': 'generatePipe',
@@ -215,12 +214,10 @@ export class CodeGenerator {
215
214
 
216
215
  // Control flow — complex
217
216
  'if': 'generateIf',
218
- 'unless': 'generateIf',
219
217
  'for-in': 'generateForIn',
220
218
  'for-of': 'generateForOf',
221
219
  'for-as': 'generateForAs',
222
220
  'while': 'generateWhile',
223
- 'until': 'generateUntil',
224
221
  'try': 'generateTry',
225
222
  'throw': 'generateThrow',
226
223
  'control': 'generateControl',
@@ -311,17 +308,16 @@ export class CodeGenerator {
311
308
  collect(sexpr);
312
309
 
313
310
  // Match output lines to collected statement locations in parallel.
314
- // Skip generated lines that have no source correspondence.
311
+ // Skip lines with no source correspondence (preamble, braces, etc.).
315
312
  let lines = code.split('\n');
316
313
  let locIdx = 0;
317
314
  for (let outLine = 0; outLine < lines.length; outLine++) {
318
315
  let line = lines[outLine];
319
316
  let trimmed = line.trim();
320
317
 
321
- // Skip lines with no source correspondence
322
318
  if (!trimmed || trimmed === '}' || trimmed === '});') continue;
323
319
  if (trimmed.startsWith('let ') || trimmed.startsWith('var ')) continue;
324
- if (trimmed.startsWith('const slice') || trimmed.startsWith('const modulo') || trimmed.startsWith('const toSearchable')) continue;
320
+ if (trimmed.startsWith('const slice') || trimmed.startsWith('const modulo') || trimmed.startsWith('const toMatchable')) continue;
325
321
  if (trimmed.startsWith('const {') && trimmed.includes('__')) continue;
326
322
  if (trimmed.startsWith('} else')) continue;
327
323
  if (trimmed.startsWith('//# source')) continue;
@@ -359,6 +355,7 @@ export class CodeGenerator {
359
355
  }
360
356
 
361
357
  if (head === 'readonly') return;
358
+ if (head === 'component') return; // Component body has its own scope
362
359
 
363
360
  if (CodeGenerator.ASSIGNMENT_OPS.has(head)) {
364
361
  let [target, value] = rest;
@@ -384,13 +381,6 @@ export class CodeGenerator {
384
381
  return;
385
382
  }
386
383
 
387
- if (head === 'unless') {
388
- let [condition, body] = rest;
389
- this.collectProgramVariables(condition);
390
- this.collectProgramVariables(body);
391
- return;
392
- }
393
-
394
384
  if (head === 'try') {
395
385
  this.collectProgramVariables(rest[0]);
396
386
  if (rest.length >= 2 && Array.isArray(rest[1]) && rest[1].length === 2 && rest[1][0] !== 'block') {
@@ -545,7 +535,7 @@ export class CodeGenerator {
545
535
  return `super.${this.currentMethodName}(${args})`;
546
536
  }
547
537
 
548
- // Postfix if/unless on single-arg call
538
+ // Postfix if on single-arg call
549
539
  if (context === 'statement' && rest.length === 1) {
550
540
  let cond = this.findPostfixConditional(rest[0]);
551
541
  if (cond) {
@@ -554,7 +544,7 @@ export class CodeGenerator {
554
544
  let condCode = this.generate(cond.condition, 'value');
555
545
  let valCode = this.generate(argWithout, 'value');
556
546
  let callStr = `${callee}(${valCode})`;
557
- return cond.type === 'unless' ? `if (!${condCode}) ${callStr}` : `if (${condCode}) ${callStr}`;
547
+ return `if (${condCode}) ${callStr}`;
558
548
  }
559
549
  }
560
550
 
@@ -567,7 +557,7 @@ export class CodeGenerator {
567
557
 
568
558
  // Statement sequence (comma operator)
569
559
  if (Array.isArray(head) && typeof head[0] === 'string') {
570
- let stmtOps = ['=', '+=', '-=', '*=', '/=', '%=', '**=', '&&=', '||=', '??=', 'if', 'unless', 'return', 'throw'];
560
+ let stmtOps = ['=', '+=', '-=', '*=', '/=', '%=', '**=', '&&=', '||=', '??=', 'if', 'return', 'throw'];
571
561
  if (stmtOps.includes(head[0])) {
572
562
  let exprs = sexpr.map(stmt => this.generate(stmt, 'value'));
573
563
  return `(${exprs.join(', ')})`;
@@ -585,7 +575,7 @@ export class CodeGenerator {
585
575
  return `new ${needsParens ? `(${ctorCode})` : ctorCode}(${args})`;
586
576
  }
587
577
 
588
- // Postfix if/unless on single-arg method call
578
+ // Postfix if on single-arg method call
589
579
  if (context === 'statement' && rest.length === 1) {
590
580
  let cond = this.findPostfixConditional(rest[0]);
591
581
  if (cond) {
@@ -594,7 +584,7 @@ export class CodeGenerator {
594
584
  let condCode = this.generate(cond.condition, 'value');
595
585
  let valCode = this.generate(argWithout, 'value');
596
586
  let callStr = `${calleeCode}(${valCode})`;
597
- return cond.type === 'unless' ? `if (!${condCode}) ${callStr}` : `if (${condCode}) ${callStr}`;
587
+ return `if (${condCode}) ${callStr}`;
598
588
  }
599
589
  }
600
590
 
@@ -638,7 +628,7 @@ export class CodeGenerator {
638
628
  }
639
629
 
640
630
  // Generate body first to detect needed helpers
641
- let blockStmts = ['def', 'class', 'if', 'unless', 'for-in', 'for-of', 'for-as', 'while', 'until', 'loop', 'switch', 'try'];
631
+ let blockStmts = ['def', 'class', 'if', 'for-in', 'for-of', 'for-as', 'while', 'loop', 'switch', 'try'];
642
632
  let statementsCode = other.map((stmt, index) => {
643
633
  let isSingle = other.length === 1 && imports.length === 0 && exports.length === 0;
644
634
  let isObj = this.is(stmt, 'object');
@@ -680,8 +670,8 @@ export class CodeGenerator {
680
670
  if (!skip) {
681
671
  if (this.helpers.has('slice')) { code += 'const slice = [].slice;\n'; needsBlank = true; }
682
672
  if (this.helpers.has('modulo')) { code += 'const modulo = (n, d) => { n = +n; d = +d; return (n % d + d) % d; };\n'; needsBlank = true; }
683
- if (this.helpers.has('toSearchable')) {
684
- code += 'const toSearchable = (v, allowNewlines) => {\n';
673
+ if (this.helpers.has('toMatchable')) {
674
+ code += 'const toMatchable = (v, allowNewlines) => {\n';
685
675
  code += ' if (typeof v === "string") return !allowNewlines && /[\\n\\r]/.test(v) ? null : v;\n';
686
676
  code += ' if (v == null) return "";\n';
687
677
  code += ' if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") return String(v);\n';
@@ -781,6 +771,13 @@ export class CodeGenerator {
781
771
  return `modulo(${this.generate(left, 'value')}, ${this.generate(right, 'value')})`;
782
772
  }
783
773
 
774
+ generateModuloAssign(head, rest) {
775
+ let [target, value] = rest;
776
+ this.helpers.add('modulo');
777
+ let t = this.generate(target, 'value'), v = this.generate(value, 'value');
778
+ return `${t} = modulo(${t}, ${v})`;
779
+ }
780
+
784
781
  generateFloorDiv(head, rest) {
785
782
  let [left, right] = rest;
786
783
  return `Math.floor(${this.generate(left, 'value')} / ${this.generate(right, 'value')})`;
@@ -870,33 +867,29 @@ export class CodeGenerator {
870
867
  }
871
868
  }
872
869
 
873
- // Postfix if/unless on assignment with || operator
870
+ // Postfix if on assignment with || operator
874
871
  if (context === 'statement' && head === '=' && Array.isArray(value) &&
875
872
  (value[0] === '||' || value[0] === '&&') && value.length === 3) {
876
873
  let [binOp, left, right] = value;
877
- if ((this.is(right, 'unless') || this.is(right, 'if')) && right.length === 3) {
878
- let [condType, condition, wrappedValue] = right;
874
+ if (this.is(right, 'if') && right.length === 3) {
875
+ let [, condition, wrappedValue] = right;
879
876
  let unwrapped = Array.isArray(wrappedValue) && wrappedValue.length === 1 ? wrappedValue[0] : wrappedValue;
880
877
  let fullValue = [binOp, left, unwrapped];
881
878
  let t = this.generate(target, 'value'), c = this.generate(condition, 'value'), v = this.generate(fullValue, 'value');
882
- return condType === 'unless' ? `if (!${c}) ${t} = ${v}` : `if (${c}) ${t} = ${v}`;
879
+ return `if (${c}) ${t} = ${v}`;
883
880
  }
884
881
  }
885
882
 
886
- // Postfix if/unless on simple assignment
883
+ // Postfix if on simple assignment
887
884
  if (context === 'statement' && head === '=' && Array.isArray(value) && value.length === 3) {
888
885
  let [valHead, condition, actualValue] = value;
889
886
  let isPostfix = Array.isArray(actualValue) && actualValue.length === 1 &&
890
887
  (!Array.isArray(actualValue[0]) || actualValue[0][0] !== 'block');
891
- if ((valHead === 'unless' || valHead === 'if') && isPostfix) {
888
+ if (valHead === 'if' && isPostfix) {
892
889
  let unwrapped = Array.isArray(actualValue) && actualValue.length === 1 ? actualValue[0] : actualValue;
893
890
  let t = this.generate(target, 'value');
894
891
  let condCode = this.unwrapLogical(this.generate(condition, 'value'));
895
892
  let v = this.generate(unwrapped, 'value');
896
- if (valHead === 'unless') {
897
- if (condCode.includes(' ') || /[<>=&|]/.test(condCode)) condCode = `(${condCode})`;
898
- return `if (!${condCode}) ${t} = ${v}`;
899
- }
900
893
  return `if (${condCode}) ${t} = ${v}`;
901
894
  }
902
895
  }
@@ -929,11 +922,17 @@ export class CodeGenerator {
929
922
 
930
923
  generatePropertyAccess(head, rest, context, sexpr) {
931
924
  let [obj, prop] = rest;
925
+ // In subclass constructors, rewrite @param refs (this.x) to _x for super() safety
926
+ if (this._atParamMap && obj === 'this') {
927
+ let mapped = this._atParamMap.get(str(prop));
928
+ if (mapped) return mapped;
929
+ }
932
930
  this.suppressReactiveUnwrap = true;
933
931
  let objCode = this.generate(obj, 'value');
934
932
  this.suppressReactiveUnwrap = false;
935
933
  let needsParens = CodeGenerator.NUMBER_LITERAL_RE.test(objCode) ||
936
- ((this.is(obj, 'object') || this.is(obj, 'await') || this.is(obj, 'yield')));
934
+ objCode.startsWith('await ') ||
935
+ ((this.is(obj, 'object') || this.is(obj, 'yield')));
937
936
  let base = needsParens ? `(${objCode})` : objCode;
938
937
  if (meta(prop, 'await') === true) return `await ${base}.${str(prop)}()`;
939
938
  if (meta(prop, 'predicate')) return `(${base}.${str(prop)} != null)`;
@@ -947,12 +946,12 @@ export class CodeGenerator {
947
946
 
948
947
  generateRegexIndex(head, rest) {
949
948
  let [value, regex, captureIndex] = rest;
950
- this.helpers.add('toSearchable');
949
+ this.helpers.add('toMatchable');
951
950
  this.programVars.add('_');
952
951
  let v = this.generate(value, 'value'), r = this.generate(regex, 'value');
953
952
  let idx = captureIndex !== null ? this.generate(captureIndex, 'value') : '0';
954
953
  let allowNL = r.includes('/m') ? ', true' : '';
955
- return `(_ = toSearchable(${v}${allowNL}).match(${r})) && _[${idx}]`;
954
+ return `(_ = toMatchable(${v}${allowNL}).match(${r})) && _[${idx}]`;
956
955
  }
957
956
 
958
957
  generateIndexAccess(head, rest) {
@@ -1062,11 +1061,6 @@ export class CodeGenerator {
1062
1061
  let [expr] = rest;
1063
1062
  if (this.sideEffectOnly) return 'return';
1064
1063
 
1065
- if (this.is(expr, 'unless')) {
1066
- let [, condition, body] = expr;
1067
- let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
1068
- return `if (!${this.generate(condition, 'value')}) return ${this.generate(val, 'value')}`;
1069
- }
1070
1064
  if (this.is(expr, 'if')) {
1071
1065
  let [, condition, body, ...elseParts] = expr;
1072
1066
  if (elseParts.length === 0) {
@@ -1074,11 +1068,10 @@ export class CodeGenerator {
1074
1068
  return `if (${this.generate(condition, 'value')}) return ${this.generate(val, 'value')}`;
1075
1069
  }
1076
1070
  }
1077
- if (this.is(expr, 'new') && Array.isArray(expr[1]) && expr[1][0] === 'unless') {
1078
- let [, unlessNode] = expr;
1079
- let [, condition, body] = unlessNode;
1071
+ if (this.is(expr, 'new') && Array.isArray(expr[1]) && expr[1][0] === 'if') {
1072
+ let [, condition, body] = expr[1];
1080
1073
  let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
1081
- return `if (!${this.generate(condition, 'value')}) return ${this.generate(['new', val], 'value')}`;
1074
+ return `if (${this.generate(condition, 'value')}) return ${this.generate(['new', val], 'value')}`;
1082
1075
  }
1083
1076
  return `return ${this.generate(expr, 'value')}`;
1084
1077
  }
@@ -1134,9 +1127,7 @@ export class CodeGenerator {
1134
1127
  // ---------------------------------------------------------------------------
1135
1128
 
1136
1129
  generateBreak() { return 'break'; }
1137
- generateBreakIf(head, rest) { return `if (${this.generate(rest[0], 'value')}) break`; }
1138
1130
  generateContinue() { return 'continue'; }
1139
- generateContinueIf(head, rest) { return `if (${this.generate(rest[0], 'value')}) continue`; }
1140
1131
 
1141
1132
  generateExistential(head, rest) {
1142
1133
  return `(${this.generate(rest[0], 'value')} != null)`;
@@ -1198,16 +1189,6 @@ export class CodeGenerator {
1198
1189
  // ---------------------------------------------------------------------------
1199
1190
 
1200
1191
  generateIf(head, rest, context, sexpr) {
1201
- if (head === 'unless') {
1202
- let [condition, body] = rest;
1203
- if (Array.isArray(body) && body.length === 1 && (!Array.isArray(body[0]) || body[0][0] !== 'block')) body = body[0];
1204
- if (context === 'value') {
1205
- return `(!${this.generate(condition, 'value')} ? ${this.extractExpression(body)} : undefined)`;
1206
- }
1207
- let condCode = this.unwrap(this.generate(condition, 'value'));
1208
- if (/[ <>=&|]/.test(condCode)) condCode = `(${condCode})`;
1209
- return `if (!${condCode}) ` + this.generate(body, 'statement');
1210
- }
1211
1192
  let [condition, thenBranch, ...elseBranches] = rest;
1212
1193
  return context === 'value'
1213
1194
  ? this.generateIfAsExpression(condition, thenBranch, elseBranches)
@@ -1431,11 +1412,6 @@ export class CodeGenerator {
1431
1412
  return code + (guard ? this.generateLoopBodyWithGuard(body, guard) : this.generateLoopBody(body));
1432
1413
  }
1433
1414
 
1434
- generateUntil(head, rest) {
1435
- let [cond, body] = rest;
1436
- return `while (!(${this.unwrap(this.generate(cond, 'value'))})) ` + this.generateLoopBody(body);
1437
- }
1438
-
1439
1415
  generateRange(head, rest) {
1440
1416
  if (head === '...') {
1441
1417
  if (rest.length === 1) return `...${this.generate(rest[0], 'value')}`;
@@ -1504,11 +1480,11 @@ export class CodeGenerator {
1504
1480
 
1505
1481
  generateRegexMatch(head, rest) {
1506
1482
  let [left, right] = rest;
1507
- this.helpers.add('toSearchable');
1483
+ this.helpers.add('toMatchable');
1508
1484
  this.programVars.add('_');
1509
1485
  let r = this.generate(right, 'value');
1510
1486
  let allowNL = r.includes('/m') ? ', true' : '';
1511
- return `(_ = toSearchable(${this.generate(left, 'value')}${allowNL}).match(${r}))`;
1487
+ return `(_ = toMatchable(${this.generate(left, 'value')}${allowNL}).match(${r}))`;
1512
1488
  }
1513
1489
 
1514
1490
  generateNew(head, rest) {
@@ -1574,7 +1550,11 @@ export class CodeGenerator {
1574
1550
  let keyCode;
1575
1551
  if (this.is(key, 'dynamicKey')) keyCode = `[${this.generate(key[1], 'value')}]`;
1576
1552
  else if (this.is(key, 'str')) keyCode = `[${this.generate(key, 'value')}]`;
1577
- else keyCode = this.generate(key, 'value');
1553
+ else {
1554
+ this.suppressReactiveUnwrap = true;
1555
+ keyCode = this.generate(key, 'value');
1556
+ this.suppressReactiveUnwrap = false;
1557
+ }
1578
1558
  let valCode = this.generate(value, 'value');
1579
1559
  if (operator === '=') return `${keyCode} = ${valCode}`;
1580
1560
  if (operator === ':') return `${keyCode}: ${valCode}`;
@@ -1642,20 +1622,18 @@ export class CodeGenerator {
1642
1622
  let [expr] = rest;
1643
1623
  if (Array.isArray(expr)) {
1644
1624
  let checkExpr = expr, wrapperType = null;
1645
- if (expr[0] === 'new' && Array.isArray(expr[1]) && (expr[1][0] === 'if' || expr[1][0] === 'unless')) {
1625
+ if (expr[0] === 'new' && Array.isArray(expr[1]) && expr[1][0] === 'if') {
1646
1626
  wrapperType = 'new'; checkExpr = expr[1];
1647
- } else if (expr[0] === 'if' || expr[0] === 'unless') {
1627
+ } else if (expr[0] === 'if') {
1648
1628
  checkExpr = expr;
1649
1629
  }
1650
- if (checkExpr[0] === 'if' || checkExpr[0] === 'unless') {
1651
- let [condType, condition, body] = checkExpr;
1630
+ if (checkExpr[0] === 'if') {
1631
+ let [, condition, body] = checkExpr;
1652
1632
  let unwrapped = Array.isArray(body) && body.length === 1 ? body[0] : body;
1653
1633
  expr = wrapperType === 'new' ? ['new', unwrapped] : unwrapped;
1654
1634
  let condCode = this.generate(condition, 'value');
1655
1635
  let throwCode = `throw ${this.generate(expr, 'value')}`;
1656
- return condType === 'unless'
1657
- ? `if (!(${condCode})) {\n${this.indent()} ${throwCode};\n${this.indent()}}`
1658
- : `if (${condCode}) {\n${this.indent()} ${throwCode};\n${this.indent()}}`;
1636
+ return `if (${condCode}) {\n${this.indent()} ${throwCode};\n${this.indent()}}`;
1659
1637
  }
1660
1638
  }
1661
1639
  let throwStmt = `throw ${this.generate(expr, 'value')}`;
@@ -1729,6 +1707,65 @@ export class CodeGenerator {
1729
1707
  // Comprehensions
1730
1708
  // ---------------------------------------------------------------------------
1731
1709
 
1710
+ // Shared: parse a for-in iterator and return { header, setup }.
1711
+ // header: the for(...) clause (no trailing brace)
1712
+ // setup: any `const x = arr[i]` preamble line, or null
1713
+ _forInHeader(vars, iterable, step) {
1714
+ let va = Array.isArray(vars) ? vars : [vars];
1715
+ let noVar = va.length === 0;
1716
+ let [itemVar, indexVar] = noVar ? ['_i', null] : va;
1717
+ let ivp = (this.is(itemVar, 'array') || this.is(itemVar, 'object'))
1718
+ ? this.generateDestructuringPattern(itemVar) : itemVar;
1719
+
1720
+ if (step && step !== null) {
1721
+ let ih = Array.isArray(iterable) && iterable[0];
1722
+ if (ih instanceof String) ih = str(ih);
1723
+ let isRange = ih === '..' || ih === '...';
1724
+ if (isRange) {
1725
+ let isExcl = ih === '...';
1726
+ let [s, e] = iterable.slice(1);
1727
+ let sc = this.generate(s, 'value'), ec = this.generate(e, 'value'), stc = this.generate(step, 'value');
1728
+ return { header: `for (let ${ivp} = ${sc}; ${ivp} ${isExcl ? '<' : '<='} ${ec}; ${ivp} += ${stc})`, setup: null };
1729
+ }
1730
+ let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
1731
+ let isNeg = this.is(step, '-', 1);
1732
+ let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
1733
+ let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
1734
+ let update = isMinus1 ? `${idxN}--` : isPlus1 ? `${idxN}++` : `${idxN} += ${stc}`;
1735
+ let header = isNeg
1736
+ ? `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${update})`
1737
+ : `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${update})`;
1738
+ return { header, setup: noVar ? null : `const ${ivp} = ${ic}[${idxN}];` };
1739
+ }
1740
+ if (indexVar) {
1741
+ let ic = this.generate(iterable, 'value');
1742
+ return {
1743
+ header: `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++)`,
1744
+ setup: `const ${ivp} = ${ic}[${indexVar}];`,
1745
+ };
1746
+ }
1747
+ return { header: `for (const ${ivp} of ${this.generate(iterable, 'value')})`, setup: null };
1748
+ }
1749
+
1750
+ // Shared: parse a for-of (object) iterator and return { header, own, vv, oc, kvp }.
1751
+ _forOfHeader(vars, iterable, own) {
1752
+ let va = Array.isArray(vars) ? vars : [vars];
1753
+ let [kv, vv] = va;
1754
+ let kvp = (this.is(kv, 'array') || this.is(kv, 'object'))
1755
+ ? this.generateDestructuringPattern(kv) : kv;
1756
+ let oc = this.generate(iterable, 'value');
1757
+ return { header: `for (const ${kvp} in ${oc})`, own, vv, oc, kvp };
1758
+ }
1759
+
1760
+ // Shared: parse a for-as (iterator) spec and return { header }.
1761
+ _forAsHeader(vars, iterable, isAwait) {
1762
+ let va = Array.isArray(vars) ? vars : [vars];
1763
+ let [fv] = va;
1764
+ let ivp = (this.is(fv, 'array') || this.is(fv, 'object'))
1765
+ ? this.generateDestructuringPattern(fv) : fv;
1766
+ return { header: `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.generate(iterable, 'value')})` };
1767
+ }
1768
+
1732
1769
  generateComprehension(head, rest, context) {
1733
1770
  let [expr, iterators, guards] = rest;
1734
1771
  if (context === 'statement') return this.generateComprehensionAsLoop(expr, iterators, guards);
@@ -1743,59 +1780,19 @@ export class CodeGenerator {
1743
1780
  for (let iter of iterators) {
1744
1781
  let [iterType, vars, iterable, stepOrOwn] = iter;
1745
1782
  if (iterType === 'for-in') {
1746
- let step = stepOrOwn;
1747
- let va = Array.isArray(vars) ? vars : [vars];
1748
- let noVar = va.length === 0;
1749
- let [itemVar, indexVar] = noVar ? ['_i', null] : va;
1750
- let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
1751
- ? this.generateDestructuringPattern(itemVar) : itemVar;
1752
-
1753
- if (step && step !== null) {
1754
- let ih = Array.isArray(iterable) && iterable[0];
1755
- if (ih instanceof String) ih = str(ih);
1756
- let isRange = ih === '..' || ih === '...';
1757
- if (isRange) {
1758
- let isExcl = ih === '...';
1759
- let [s, e] = iterable.slice(1);
1760
- let sc = this.generate(s, 'value'), ec = this.generate(e, 'value'), stc = this.generate(step, 'value');
1761
- code += this.indent() + `for (let ${ivp} = ${sc}; ${ivp} ${isExcl ? '<' : '<='} ${ec}; ${ivp} += ${stc}) {\n`;
1762
- this.indentLevel++;
1763
- } else {
1764
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
1765
- let isNeg = this.is(step, '-', 1);
1766
- code += isNeg
1767
- ? this.indent() + `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) {\n`
1768
- : this.indent() + `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) {\n`;
1769
- this.indentLevel++;
1770
- if (!noVar) code += this.indent() + `const ${ivp} = ${ic}[${idxN}];\n`;
1771
- }
1772
- } else if (indexVar) {
1773
- let ic = this.generate(iterable, 'value');
1774
- code += this.indent() + `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++) {\n`;
1775
- this.indentLevel++;
1776
- code += this.indent() + `const ${ivp} = ${ic}[${indexVar}];\n`;
1777
- } else {
1778
- code += this.indent() + `for (const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
1779
- this.indentLevel++;
1780
- }
1783
+ let { header, setup } = this._forInHeader(vars, iterable, stepOrOwn);
1784
+ code += this.indent() + header + ' {\n';
1785
+ this.indentLevel++;
1786
+ if (setup) code += this.indent() + setup + '\n';
1781
1787
  } else if (iterType === 'for-of') {
1782
- let own = stepOrOwn;
1783
- let va = Array.isArray(vars) ? vars : [vars];
1784
- let [kv, vv] = va;
1785
- let kvp = ((this.is(kv, 'array') || this.is(kv, 'object')))
1786
- ? this.generateDestructuringPattern(kv) : kv;
1787
- let oc = this.generate(iterable, 'value');
1788
- code += this.indent() + `for (const ${kvp} in ${oc}) {\n`;
1788
+ let { header, own, vv, oc, kvp } = this._forOfHeader(vars, iterable, stepOrOwn);
1789
+ code += this.indent() + header + ' {\n';
1789
1790
  this.indentLevel++;
1790
1791
  if (own) code += this.indent() + `if (!Object.hasOwn(${oc}, ${kvp})) continue;\n`;
1791
1792
  if (vv) code += this.indent() + `const ${vv} = ${oc}[${kvp}];\n`;
1792
1793
  } else if (iterType === 'for-as') {
1793
- let isAwait = iter[3];
1794
- let va = Array.isArray(vars) ? vars : [vars];
1795
- let [fv] = va;
1796
- let ivp = ((this.is(fv, 'array') || this.is(fv, 'object')))
1797
- ? this.generateDestructuringPattern(fv) : fv;
1798
- code += this.indent() + `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
1794
+ let { header } = this._forAsHeader(vars, iterable, iter[3]);
1795
+ code += this.indent() + header + ' {\n';
1799
1796
  this.indentLevel++;
1800
1797
  }
1801
1798
  }
@@ -1808,12 +1805,12 @@ export class CodeGenerator {
1808
1805
  let hasCtrl = (node) => {
1809
1806
  if (typeof node === 'string' && (node === 'break' || node === 'continue')) return true;
1810
1807
  if (!Array.isArray(node)) return false;
1811
- if (['break', 'continue', 'break-if', 'continue-if', 'return', 'throw'].includes(node[0])) return true;
1812
- if (node[0] === 'if' || node[0] === 'unless') return node.slice(1).some(hasCtrl);
1808
+ if (['break', 'continue', 'return', 'throw'].includes(node[0])) return true;
1809
+ if (node[0] === 'if') return node.slice(1).some(hasCtrl);
1813
1810
  return node.some(hasCtrl);
1814
1811
  };
1815
1812
 
1816
- let loopStmts = ['for-in', 'for-of', 'for-as', 'while', 'until', 'loop'];
1813
+ let loopStmts = ['for-in', 'for-of', 'for-as', 'while', 'loop'];
1817
1814
  if (this.is(expr, 'block')) {
1818
1815
  for (let i = 0; i < expr.length - 1; i++) {
1819
1816
  let s = expr[i + 1], isLast = i === expr.length - 2;
@@ -1909,17 +1906,36 @@ export class CodeGenerator {
1909
1906
  let hasAwait = this.containsAwait(body), hasYield = this.containsYield(body);
1910
1907
  let cleanParams = params, autoAssign = [];
1911
1908
  if (mName === 'constructor') {
1909
+ let isSubclass = !!parentClass;
1910
+ let atParamMap = isSubclass ? new Map() : null;
1912
1911
  cleanParams = params.map(p => {
1913
- if (this.is(p, '.') && p[1] === 'this') { autoAssign.push(`this.${p[2]} = ${p[2]}`); return p[2]; }
1912
+ // Handle @param: ['.', 'this', 'name']
1913
+ if (this.is(p, '.') && p[1] === 'this') {
1914
+ let name = p[2];
1915
+ let param = isSubclass ? `_${name}` : name;
1916
+ autoAssign.push(`this.${name} = ${param}`);
1917
+ if (isSubclass) atParamMap.set(name, param);
1918
+ return param;
1919
+ }
1920
+ // Handle @param with default: ['default', ['.', 'this', 'name'], value]
1921
+ if (this.is(p, 'default') && this.is(p[1], '.') && p[1][1] === 'this') {
1922
+ let name = p[1][2];
1923
+ let param = isSubclass ? `_${name}` : name;
1924
+ autoAssign.push(`this.${name} = ${param}`);
1925
+ if (isSubclass) atParamMap.set(name, param);
1926
+ return ['default', param, p[2]];
1927
+ }
1914
1928
  return p;
1915
1929
  });
1916
1930
  for (let bm of boundMethods) autoAssign.unshift(`this.${bm} = this.${bm}.bind(this)`);
1931
+ if (atParamMap?.size > 0) this._atParamMap = atParamMap;
1917
1932
  }
1918
1933
  let pList = this.generateParamList(cleanParams);
1919
1934
  let prefix = (isStatic ? 'static ' : '') + (hasAwait ? 'async ' : '') + (hasYield ? '*' : '');
1920
1935
  code += this.indent() + `${prefix}${mName}(${pList}) `;
1921
1936
  if (!isComputed) this.currentMethodName = mName;
1922
1937
  code += this.generateMethodBody(body, autoAssign, mName === 'constructor', cleanParams);
1938
+ this._atParamMap = null;
1923
1939
  this.currentMethodName = null;
1924
1940
  code += '\n';
1925
1941
  } else if (isStatic) {
@@ -2098,7 +2114,7 @@ export class CodeGenerator {
2098
2114
  findPostfixConditional(expr) {
2099
2115
  if (!Array.isArray(expr)) return null;
2100
2116
  let h = expr[0];
2101
- if ((h === 'unless' || h === 'if') && expr.length === 3) return {type: h, condition: expr[1], value: expr[2]};
2117
+ if (h === 'if' && expr.length === 3) return {type: h, condition: expr[1], value: expr[2]};
2102
2118
  if (h === '+' || h === '-' || h === '*' || h === '/') {
2103
2119
  for (let i = 1; i < expr.length; i++) {
2104
2120
  let found = this.findPostfixConditional(expr[i]);
@@ -2192,7 +2208,7 @@ export class CodeGenerator {
2192
2208
  !this.scopeStack.some(s => s.has(v)) // don't re-declare variables from enclosing scopes
2193
2209
  ));
2194
2210
  let noRetStmts = ['return', 'throw', 'break', 'continue'];
2195
- let loopStmts = ['for-in', 'for-of', 'for-as', 'while', 'until', 'loop'];
2211
+ let loopStmts = ['for-in', 'for-of', 'for-as', 'while', 'loop'];
2196
2212
 
2197
2213
  // Track this function's scope so nested functions don't re-declare its variables
2198
2214
  this.scopeStack.push(new Set([...newVars, ...paramNames]));
@@ -2240,7 +2256,7 @@ export class CodeGenerator {
2240
2256
  return;
2241
2257
  }
2242
2258
 
2243
- if (!isConstructor && !sideEffectOnly && isLast && (h === 'if' || h === 'unless')) {
2259
+ if (!isConstructor && !sideEffectOnly && isLast && h === 'if') {
2244
2260
  let [cond, thenB, ...elseB] = stmt.slice(1);
2245
2261
  let hasMulti = (b) => this.is(b, 'block') && b.length > 2;
2246
2262
  if (hasMulti(thenB) || elseB.some(hasMulti)) {
@@ -2299,7 +2315,13 @@ export class CodeGenerator {
2299
2315
  // Single expression
2300
2316
  this.sideEffectOnly = prevSEO;
2301
2317
  let result;
2302
- if (isConstructor || this.hasExplicitControlFlow(body)) result = `{ ${this.generate(body, 'statement')}; }`;
2318
+ if (isConstructor && autoAssignments.length > 0) {
2319
+ // Constructor with @params as a single expression — need to emit autoAssignments
2320
+ let isSuper = Array.isArray(body) && body[0] === 'super';
2321
+ let bodyCode = this.generate(body, 'statement');
2322
+ let assigns = autoAssignments.map(a => `${a};`).join(' ');
2323
+ result = isSuper ? `{ ${bodyCode}; ${assigns} }` : `{ ${assigns} ${bodyCode}; }`;
2324
+ } else if (isConstructor || this.hasExplicitControlFlow(body)) result = `{ ${this.generate(body, 'statement')}; }`;
2303
2325
  else if (Array.isArray(body) && (noRetStmts.includes(body[0]) || loopStmts.includes(body[0]))) result = `{ ${this.generate(body, 'statement')}; }`;
2304
2326
  else if (sideEffectOnly) result = `{ ${this.generate(body, 'statement')}; return; }`;
2305
2327
  else result = `{ return ${this.generate(body, 'value')}; }`;
@@ -2379,34 +2401,10 @@ export class CodeGenerator {
2379
2401
  if (iterators.length === 1) {
2380
2402
  let [iterType, vars, iterable, stepOrOwn] = iterators[0];
2381
2403
  if (iterType === 'for-in') {
2382
- let step = stepOrOwn;
2383
- let va = Array.isArray(vars) ? vars : [vars];
2384
- let noVar = va.length === 0;
2385
- let [itemVar, indexVar] = noVar ? ['_i', null] : va;
2386
- let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
2387
- ? this.generateDestructuringPattern(itemVar) : itemVar;
2388
-
2389
- if (step && step !== null) {
2390
- let ih = Array.isArray(iterable) && iterable[0];
2391
- if (ih instanceof String) ih = str(ih);
2392
- let isRange = ih === '..' || ih === '...';
2393
- if (isRange) {
2394
- let isExcl = ih === '...';
2395
- let [s, e] = iterable.slice(1);
2396
- code += this.indent() + `for (let ${ivp} = ${this.generate(s, 'value')}; ${ivp} ${isExcl ? '<' : '<='} ${this.generate(e, 'value')}; ${ivp} += ${this.generate(step, 'value')}) {\n`;
2397
- } else {
2398
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
2399
- let isNeg = this.is(step, '-', 1);
2400
- code += isNeg
2401
- ? this.indent() + `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) {\n`
2402
- : this.indent() + `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) {\n`;
2403
- this.indentLevel++;
2404
- if (!noVar) code += this.indent() + `const ${ivp} = ${ic}[${idxN}];\n`;
2405
- }
2406
- } else {
2407
- code += this.indent() + `for (const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
2408
- }
2404
+ let { header, setup } = this._forInHeader(vars, iterable, stepOrOwn);
2405
+ code += this.indent() + header + ' {\n';
2409
2406
  this.indentLevel++;
2407
+ if (setup) code += this.indent() + setup + '\n';
2410
2408
  if (guards && guards.length > 0) {
2411
2409
  code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2412
2410
  this.indentLevel++;
@@ -2423,155 +2421,51 @@ export class CodeGenerator {
2423
2421
 
2424
2422
  generateComprehensionAsLoop(expr, iterators, guards) {
2425
2423
  let code = '';
2424
+ let guardCond = guards?.length ? guards.map(g => this.generate(g, 'value')).join(' && ') : null;
2425
+
2426
+ // Helper: emit the loop body with optional guard wrapping
2427
+ let emitBody = () => {
2428
+ if (guardCond) {
2429
+ code += this.indent() + `if (${guardCond}) {\n`;
2430
+ this.indentLevel++;
2431
+ code += this.indent() + this.generate(expr, 'statement') + ';\n';
2432
+ this.indentLevel--; code += this.indent() + '}\n';
2433
+ } else {
2434
+ code += this.indent() + this.generate(expr, 'statement') + ';\n';
2435
+ }
2436
+ };
2437
+
2426
2438
  if (iterators.length === 1) {
2427
2439
  let [iterType, vars, iterable, stepOrOwn] = iterators[0];
2428
2440
 
2429
2441
  if (iterType === 'for-in') {
2430
- let step = stepOrOwn;
2431
- let va = Array.isArray(vars) ? vars : [vars];
2432
- let noVar = va.length === 0;
2433
- let [itemVar, indexVar] = noVar ? ['_i', null] : va;
2434
- let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
2435
- ? this.generateDestructuringPattern(itemVar) : itemVar;
2436
-
2437
- if (step && step !== null) {
2438
- let ih = Array.isArray(iterable) && iterable[0];
2439
- if (ih instanceof String) ih = str(ih);
2440
- let isRange = ih === '..' || ih === '...';
2441
- if (isRange) {
2442
- let isExcl = ih === '...';
2443
- let [s, e] = iterable.slice(1);
2444
- code += `for (let ${ivp} = ${this.generate(s, 'value')}; ${ivp} ${isExcl ? '<' : '<='} ${this.generate(e, 'value')}; ${ivp} += ${this.generate(step, 'value')}) `;
2445
- } else {
2446
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
2447
- let isNeg = this.is(step, '-', 1);
2448
- let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
2449
- let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
2450
- if (isMinus1) code += `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN}--) `;
2451
- else if (isPlus1) code += `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN}++) `;
2452
- else if (isNeg) code += `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) `;
2453
- else code += `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) `;
2454
- code += '{\n';
2455
- this.indentLevel++;
2456
- if (!noVar) code += this.indent() + `const ${ivp} = ${ic}[${idxN}];\n`;
2457
- }
2458
- if (guards?.length) {
2459
- if (!isRange) code += this.indent();
2460
- code += '{\n'; this.indentLevel++;
2461
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2462
- this.indentLevel++;
2463
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2464
- this.indentLevel--; code += this.indent() + '}\n';
2465
- this.indentLevel--; code += this.indent() + '}';
2466
- } else {
2467
- if (!isRange) code += this.indent();
2468
- code += '{\n'; this.indentLevel++;
2469
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2470
- this.indentLevel--; code += this.indent() + '}';
2471
- }
2472
- if (!isRange) { this.indentLevel--; code += '\n' + this.indent() + '}'; }
2473
- return code;
2474
- }
2475
-
2476
- if (indexVar) {
2477
- let ic = this.generate(iterable, 'value');
2478
- code += `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++) `;
2479
- code += '{\n'; this.indentLevel++;
2480
- code += this.indent() + `const ${ivp} = ${ic}[${indexVar}];\n`;
2481
- } else {
2482
- code += `for (const ${ivp} of ${this.generate(iterable, 'value')}) `;
2483
- if (guards?.length) {
2484
- code += '{\n'; this.indentLevel++;
2485
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2486
- this.indentLevel++;
2487
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2488
- this.indentLevel--; code += this.indent() + '}\n';
2489
- this.indentLevel--; code += this.indent() + '}';
2490
- } else {
2491
- code += '{\n'; this.indentLevel++;
2492
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2493
- this.indentLevel--; code += this.indent() + '}';
2494
- }
2495
- return code;
2496
- }
2497
-
2498
- // Fall through for indexVar case
2499
- if (guards?.length) {
2500
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2501
- this.indentLevel++;
2502
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2503
- this.indentLevel--; code += this.indent() + '}\n';
2504
- } else {
2505
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2506
- }
2442
+ let { header, setup } = this._forInHeader(vars, iterable, stepOrOwn);
2443
+ code += header + ' {\n';
2444
+ this.indentLevel++;
2445
+ if (setup) code += this.indent() + setup + '\n';
2446
+ emitBody();
2507
2447
  this.indentLevel--;
2508
2448
  code += this.indent() + '}';
2509
2449
  return code;
2510
2450
  }
2511
2451
 
2512
2452
  if (iterType === 'for-as') {
2513
- let va = Array.isArray(vars) ? vars : [vars];
2514
- let [fv] = va;
2515
- let ivp = ((this.is(fv, 'array') || this.is(fv, 'object')))
2516
- ? this.generateDestructuringPattern(fv) : fv;
2517
- code += `for (const ${ivp} of ${this.generate(iterable, 'value')}) `;
2518
- if (guards?.length) {
2519
- code += '{\n'; this.indentLevel++;
2520
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2521
- this.indentLevel++;
2522
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2523
- this.indentLevel--; code += this.indent() + '}\n';
2524
- this.indentLevel--; code += this.indent() + '}';
2525
- } else {
2526
- code += '{\n'; this.indentLevel++;
2527
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2528
- this.indentLevel--; code += this.indent() + '}';
2529
- }
2453
+ let { header } = this._forAsHeader(vars, iterable, stepOrOwn);
2454
+ code += header + ' {\n';
2455
+ this.indentLevel++;
2456
+ emitBody();
2457
+ this.indentLevel--;
2458
+ code += this.indent() + '}';
2530
2459
  return code;
2531
2460
  }
2532
2461
 
2533
2462
  if (iterType === 'for-of') {
2534
- let va = Array.isArray(vars) ? vars : [vars];
2535
- let [kv, vv] = va;
2536
- let own = stepOrOwn;
2537
- let oc = this.generate(iterable, 'value');
2538
- code += `for (const ${kv} in ${oc}) {\n`;
2463
+ let { header, own, vv, oc, kvp } = this._forOfHeader(vars, iterable, stepOrOwn);
2464
+ code += header + ' {\n';
2539
2465
  this.indentLevel++;
2540
- if (own && !vv && !guards?.length) {
2541
- code += this.indent() + `if (!Object.hasOwn(${oc}, ${kv})) continue;\n`;
2542
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2543
- } else if (own && vv && guards?.length) {
2544
- code += this.indent() + `if (Object.hasOwn(${oc}, ${kv})) {\n`;
2545
- this.indentLevel++;
2546
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2547
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2548
- this.indentLevel++;
2549
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2550
- this.indentLevel--; code += this.indent() + '}\n';
2551
- this.indentLevel--; code += this.indent() + '}\n';
2552
- } else if (own && vv) {
2553
- code += this.indent() + `if (Object.hasOwn(${oc}, ${kv})) {\n`;
2554
- this.indentLevel++;
2555
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2556
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2557
- this.indentLevel--; code += this.indent() + '}\n';
2558
- } else if (vv && guards?.length) {
2559
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2560
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2561
- this.indentLevel++;
2562
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2563
- this.indentLevel--; code += this.indent() + '}\n';
2564
- } else if (vv) {
2565
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2566
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2567
- } else if (guards?.length) {
2568
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2569
- this.indentLevel++;
2570
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2571
- this.indentLevel--; code += this.indent() + '}\n';
2572
- } else {
2573
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2574
- }
2466
+ if (own) code += this.indent() + `if (!Object.hasOwn(${oc}, ${kvp})) continue;\n`;
2467
+ if (vv) code += this.indent() + `const ${vv} = ${oc}[${kvp}];\n`;
2468
+ emitBody();
2575
2469
  this.indentLevel--;
2576
2470
  code += this.indent() + '}';
2577
2471
  return code;
@@ -2588,7 +2482,7 @@ export class CodeGenerator {
2588
2482
  generateIfElseWithEarlyReturns(ifStmt) {
2589
2483
  let [head, condition, thenBranch, ...elseBranches] = ifStmt;
2590
2484
  let code = '';
2591
- let condCode = head === 'unless' ? `!${this.generate(condition, 'value')}` : this.generate(condition, 'value');
2485
+ let condCode = this.generate(condition, 'value');
2592
2486
  code += this.indent() + `if (${condCode}) {\n`;
2593
2487
  code += this.withIndent(() => this.generateBranchWithReturn(thenBranch));
2594
2488
  code += this.indent() + '}';
@@ -2738,7 +2632,7 @@ export class CodeGenerator {
2738
2632
  if (!generated || generated.endsWith(';')) return false;
2739
2633
  if (!generated.endsWith('}')) return true;
2740
2634
  let h = Array.isArray(stmt) ? stmt[0] : null;
2741
- return !['def', 'class', 'if', 'unless', 'for-in', 'for-of', 'for-as', 'while', 'until', 'loop', 'switch', 'try'].includes(h);
2635
+ return !['def', 'class', 'if', 'for-in', 'for-of', 'for-as', 'while', 'loop', 'switch', 'try'].includes(h);
2742
2636
  }
2743
2637
 
2744
2638
  addSemicolon(stmt, generated) { return generated + (this.needsSemicolon(stmt, generated) ? ';' : ''); }
@@ -2765,7 +2659,7 @@ export class CodeGenerator {
2765
2659
  return stmts.some(s => Array.isArray(s) && ['return', 'throw', 'break', 'continue'].includes(s[0]));
2766
2660
  });
2767
2661
  }
2768
- if (t === 'if' || t === 'unless') {
2662
+ if (t === 'if') {
2769
2663
  let [, , thenB, elseB] = body;
2770
2664
  return this.branchHasControlFlow(thenB) && elseB && this.branchHasControlFlow(elseB);
2771
2665
  }
@@ -3154,14 +3048,19 @@ function __effect(fn) {
3154
3048
  dependencies: new Set(),
3155
3049
 
3156
3050
  run() {
3051
+ if (effect._cleanup) { effect._cleanup(); effect._cleanup = null; }
3157
3052
  for (const dep of effect.dependencies) dep.delete(effect);
3158
3053
  effect.dependencies.clear();
3159
3054
  const prev = __currentEffect;
3160
3055
  __currentEffect = effect;
3161
- try { fn(); } finally { __currentEffect = prev; }
3056
+ try {
3057
+ const result = fn();
3058
+ if (typeof result === 'function') effect._cleanup = result;
3059
+ } finally { __currentEffect = prev; }
3162
3060
  },
3163
3061
 
3164
3062
  dispose() {
3063
+ if (effect._cleanup) { effect._cleanup(); effect._cleanup = null; }
3165
3064
  for (const dep of effect.dependencies) dep.delete(effect);
3166
3065
  effect.dependencies.clear();
3167
3066
  }