rip-lang 3.13.133 → 3.13.135

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
@@ -1,6 +1,6 @@
1
1
  // Rip Compiler — S-expression → JavaScript
2
2
  //
3
- // Architecture: Lexer (tokenize) → Parser (parse) → CodeGenerator (compile) → JavaScript
3
+ // Architecture: Lexer (tokenize) → Parser (parse) → CodeEmitter (compile) → JavaScript
4
4
  //
5
5
  // Metadata bridge: The lexer stores token metadata in .data objects. The Compiler
6
6
  // class's lexer adapter reconstructs new String() wrapping so grammar actions pass
@@ -11,8 +11,9 @@
11
11
  import { Lexer } from './lexer.js';
12
12
  import { parser } from './parser.js';
13
13
  import { installComponentSupport } from './components.js';
14
- import { emitTypes, generateEnum } from './types.js';
14
+ import { emitTypes, emitEnum } from './types.js';
15
15
  import { SourceMapGenerator } from './sourcemaps.js';
16
+ import { RipError, toRipError } from './error.js';
16
17
 
17
18
  // =============================================================================
18
19
  // Metadata helpers — isolate all new String() awareness here
@@ -28,7 +29,7 @@ let str = (node) => node instanceof String ? node.valueOf() : node;
28
29
  let INLINE_FORMS = new Set([
29
30
  '+', '-', '*', '/', '%', '//', '%%', '**',
30
31
  '==', '!=', '<', '>', '<=', '>=', '===', '!==',
31
- '&&', '||', '??', '!?', 'not',
32
+ '&&', '||', '??', 'not',
32
33
  '&', '|', '^', '<<', '>>', '>>>',
33
34
  '=', '.', '?.', '[]',
34
35
  '!', 'typeof', 'void', 'delete', 'new',
@@ -125,7 +126,7 @@ function formatSExpr(arr, indent = 0, isTopLevel = false) {
125
126
  // Code Generator
126
127
  // =============================================================================
127
128
 
128
- export class CodeGenerator {
129
+ export class CodeEmitter {
129
130
 
130
131
  static ASSIGNMENT_OPS = new Set([
131
132
  '=', '+=', '-=', '*=', '/=', '?=', '&=', '|=', '^=', '%=',
@@ -137,132 +138,131 @@ export class CodeGenerator {
137
138
 
138
139
  // Dispatch table: s-expression head → method name
139
140
  static GENERATORS = {
140
- 'program': 'generateProgram',
141
+ 'program': 'emitProgram',
141
142
 
142
143
  // Logical (flatten chains)
143
- '&&': 'generateLogicalAnd',
144
- '||': 'generateLogicalOr',
144
+ '&&': 'emitLogicalAnd',
145
+ '||': 'emitLogicalOr',
145
146
 
146
147
  // Binary operators (shared)
147
- '+': 'generateBinaryOp', '-': 'generateBinaryOp', '*': 'generateBinaryOp',
148
- '/': 'generateBinaryOp', '%': 'generateBinaryOp', '**': 'generateBinaryOp',
149
- '==': 'generateBinaryOp', '===': 'generateBinaryOp', '!=': 'generateBinaryOp',
150
- '!==': 'generateBinaryOp', '<': 'generateBinaryOp', '>': 'generateBinaryOp',
151
- '<=': 'generateBinaryOp', '>=': 'generateBinaryOp', '??': 'generateBinaryOp',
152
- '!?': 'generateBinaryOp', '&': 'generateBinaryOp', '|': 'generateBinaryOp',
153
- '^': 'generateBinaryOp', '<<': 'generateBinaryOp', '>>': 'generateBinaryOp',
154
- '>>>': 'generateBinaryOp',
148
+ '+': 'emitBinaryOp', '-': 'emitBinaryOp', '*': 'emitBinaryOp',
149
+ '/': 'emitBinaryOp', '%': 'emitBinaryOp', '**': 'emitBinaryOp',
150
+ '==': 'emitBinaryOp', '===': 'emitBinaryOp', '!=': 'emitBinaryOp',
151
+ '!==': 'emitBinaryOp', '<': 'emitBinaryOp', '>': 'emitBinaryOp',
152
+ '<=': 'emitBinaryOp', '>=': 'emitBinaryOp', '??': 'emitBinaryOp',
153
+ '&': 'emitBinaryOp', '|': 'emitBinaryOp',
154
+ '^': 'emitBinaryOp', '<<': 'emitBinaryOp', '>>': 'emitBinaryOp',
155
+ '>>>': 'emitBinaryOp',
155
156
 
156
157
  // Special operators
157
- '%%': 'generateModulo',
158
- '%%=': 'generateModuloAssign',
159
- '//': 'generateFloorDiv',
160
- '//=': 'generateFloorDivAssign',
161
- '..': 'generateRange',
158
+ '%%': 'emitModulo',
159
+ '%%=': 'emitModuloAssign',
160
+ '//': 'emitFloorDiv',
161
+ '//=': 'emitFloorDivAssign',
162
+ '..': 'emitRange',
162
163
 
163
164
  // Assignment (shared)
164
- '=': 'generateAssignment',
165
- '+=': 'generateAssignment', '-=': 'generateAssignment', '*=': 'generateAssignment',
166
- '/=': 'generateAssignment', '%=': 'generateAssignment', '**=': 'generateAssignment',
167
- '&&=': 'generateAssignment', '||=': 'generateAssignment', '??=': 'generateAssignment',
168
- '?=': 'generateAssignment', '&=': 'generateAssignment', '|=': 'generateAssignment',
169
- '^=': 'generateAssignment', '<<=': 'generateAssignment', '>>=': 'generateAssignment',
170
- '>>>=': 'generateAssignment',
171
-
172
- '...': 'generateRange',
173
- '!': 'generateNot',
174
- '~': 'generateBitwiseNot',
175
- '++': 'generateIncDec',
176
- '--': 'generateIncDec',
177
- '=~': 'generateRegexMatch',
178
- 'instanceof': 'generateInstanceof',
179
- 'in': 'generateIn',
180
- 'of': 'generateOf',
181
- 'typeof': 'generateTypeof',
182
- 'delete': 'generateDelete',
183
- 'new': 'generateNew',
165
+ '=': 'emitAssignment',
166
+ '+=': 'emitAssignment', '-=': 'emitAssignment', '*=': 'emitAssignment',
167
+ '/=': 'emitAssignment', '%=': 'emitAssignment', '**=': 'emitAssignment',
168
+ '&&=': 'emitAssignment', '||=': 'emitAssignment', '??=': 'emitAssignment',
169
+ '?=': 'emitAssignment', '&=': 'emitAssignment', '|=': 'emitAssignment',
170
+ '^=': 'emitAssignment', '<<=': 'emitAssignment', '>>=': 'emitAssignment',
171
+ '>>>=': 'emitAssignment',
172
+
173
+ '...': 'emitRange',
174
+ '!': 'emitNot',
175
+ '~': 'emitBitwiseNot',
176
+ '++': 'emitIncDec',
177
+ '--': 'emitIncDec',
178
+ '=~': 'emitRegexMatch',
179
+ 'instanceof': 'emitInstanceof',
180
+ 'in': 'emitIn',
181
+ 'of': 'emitOf',
182
+ 'typeof': 'emitTypeof',
183
+ 'delete': 'emitDelete',
184
+ 'new': 'emitNew',
184
185
 
185
186
  // Data structures
186
- 'array': 'generateArray',
187
- 'object': 'generateObject',
188
- 'map-literal': 'generateMap',
189
- 'block': 'generateBlock',
187
+ 'array': 'emitArray',
188
+ 'object': 'emitObject',
189
+ 'map-literal': 'emitMap',
190
+ 'block': 'emitBlock',
190
191
 
191
192
  // Property access
192
- '.': 'generatePropertyAccess',
193
- '?.': 'generateOptionalProperty',
194
- '[]': 'generateIndexAccess',
195
- 'optindex': 'generateOptIndex',
196
- 'optcall': 'generateOptCall',
197
- 'regex-index': 'generateRegexIndex',
193
+ '.': 'emitPropertyAccess',
194
+ '?.': 'emitOptionalProperty',
195
+ '[]': 'emitIndexAccess',
196
+ 'optindex': 'emitOptIndex',
197
+ 'optcall': 'emitOptCall',
198
+ 'regex-index': 'emitRegexIndex',
198
199
 
199
200
  // Functions
200
- 'def': 'generateDef',
201
- '->': 'generateThinArrow',
202
- '=>': 'generateFatArrow',
203
- 'return': 'generateReturn',
201
+ 'def': 'emitDef',
202
+ '->': 'emitThinArrow',
203
+ '=>': 'emitFatArrow',
204
+ 'return': 'emitReturn',
204
205
 
205
206
  // Reactive
206
- 'state': 'generateState',
207
- 'computed': 'generateComputed',
208
- 'readonly': 'generateReadonly',
209
- 'effect': 'generateEffect',
207
+ 'state': 'emitState',
208
+ 'computed': 'emitComputed',
209
+ 'readonly': 'emitReadonly',
210
+ 'effect': 'emitEffect',
210
211
 
211
212
  // Control flow — simple
212
- 'break': 'generateBreak',
213
- 'continue': 'generateContinue',
214
- '?': 'generateExistential',
215
- 'defined': 'generateDefined',
216
- 'presence': 'generatePresence',
217
- '?:': 'generateTernary',
218
- '|>': 'generatePipe',
219
- 'loop': 'generateLoop',
220
- 'loop-n': 'generateLoopN',
221
- 'await': 'generateAwait',
222
- 'yield': 'generateYield',
223
- 'yield-from': 'generateYieldFrom',
213
+ 'break': 'emitBreak',
214
+ 'continue': 'emitContinue',
215
+ '?': 'emitExistential',
216
+ 'presence': 'emitPresence',
217
+ '?:': 'emitTernary',
218
+ '|>': 'emitPipe',
219
+ 'loop': 'emitLoop',
220
+ 'loop-n': 'emitLoopN',
221
+ 'await': 'emitAwait',
222
+ 'yield': 'emitYield',
223
+ 'yield-from': 'emitYieldFrom',
224
224
 
225
225
  // Control flow — complex
226
- 'if': 'generateIf',
227
- 'for-in': 'generateForIn',
228
- 'for-of': 'generateForOf',
229
- 'for-as': 'generateForAs',
230
- 'while': 'generateWhile',
231
- 'try': 'generateTry',
232
- 'throw': 'generateThrow',
233
- 'control': 'generateControl',
234
- 'switch': 'generateSwitch',
235
- 'when': 'generateWhen',
226
+ 'if': 'emitIf',
227
+ 'for-in': 'emitForIn',
228
+ 'for-of': 'emitForOf',
229
+ 'for-as': 'emitForAs',
230
+ 'while': 'emitWhile',
231
+ 'try': 'emitTry',
232
+ 'throw': 'emitThrow',
233
+ 'control': 'emitControl',
234
+ 'switch': 'emitSwitch',
235
+ 'when': 'emitWhen',
236
236
 
237
237
  // Comprehensions
238
- 'comprehension': 'generateComprehension',
239
- 'object-comprehension': 'generateObjectComprehension',
238
+ 'comprehension': 'emitComprehension',
239
+ 'object-comprehension': 'emitObjectComprehension',
240
240
 
241
241
  // Classes
242
- 'class': 'generateClass',
243
- 'super': 'generateSuper',
242
+ 'class': 'emitClass',
243
+ 'super': 'emitSuper',
244
244
 
245
245
  // Components
246
- 'component': 'generateComponent',
247
- 'render': 'generateRender',
248
- 'offer': 'generateOffer',
249
- 'accept': 'generateAccept',
246
+ 'component': 'emitComponent',
247
+ 'render': 'emitRender',
248
+ 'offer': 'emitOffer',
249
+ 'accept': 'emitAccept',
250
250
 
251
251
  // Types
252
- 'enum': 'generateEnum',
252
+ 'enum': 'emitEnum',
253
253
 
254
254
  // Modules
255
- 'import': 'generateImport',
256
- 'export': 'generateExport',
257
- 'export-default': 'generateExportDefault',
258
- 'export-all': 'generateExportAll',
259
- 'export-from': 'generateExportFrom',
255
+ 'import': 'emitImport',
256
+ 'export': 'emitExport',
257
+ 'export-default': 'emitExportDefault',
258
+ 'export-all': 'emitExportAll',
259
+ 'export-from': 'emitExportFrom',
260
260
 
261
261
  // Special forms
262
- 'do-iife': 'generateDoIIFE',
263
- 'regex': 'generateRegex',
264
- 'tagged-template': 'generateTaggedTemplate',
265
- 'str': 'generateString',
262
+ 'do-iife': 'emitDoIIFE',
263
+ 'regex': 'emitRegex',
264
+ 'tagged-template': 'emitTaggedTemplate',
265
+ 'str': 'emitString',
266
266
  };
267
267
 
268
268
  constructor(options = {}) {
@@ -277,6 +277,11 @@ export class CodeGenerator {
277
277
  }
278
278
  }
279
279
 
280
+ // Throw a RipError with source location from the nearest s-expression node
281
+ error(message, sexpr, { suggestion } = {}) {
282
+ throw RipError.fromSExpr(message, sexpr, this.options.source, this.options.filename, suggestion);
283
+ }
284
+
280
285
  // ---------------------------------------------------------------------------
281
286
  // Entry point
282
287
  // ---------------------------------------------------------------------------
@@ -287,7 +292,7 @@ export class CodeGenerator {
287
292
  this.helpers = new Set();
288
293
  this.scopeStack = []; // Track enclosing function scopes for proper variable hoisting
289
294
  this.collectProgramVariables(sexpr);
290
- let code = this.generate(sexpr);
295
+ let code = this.emit(sexpr);
291
296
 
292
297
  // Build source map mappings from generation-time recorded entries
293
298
  if (this.sourceMap) this.buildMappings();
@@ -371,8 +376,12 @@ export class CodeGenerator {
371
376
  }
372
377
  if (ident) result.push({ name: ident, origLine: node.loc.r, origCol: node.loc.c });
373
378
  }
374
- // Recurse into children (skip head at index 0 — already processed via parent)
375
- for (let i = 1; i < node.length; i++) {
379
+ // Recurse into children (skip head at index 0 — already processed via parent).
380
+ // For arrow functions (-> / =>), skip index 1 (params array) — parameter
381
+ // names are not call expressions and would produce incorrect mappings via
382
+ // the distance heuristic when the same identifier appears in other methods.
383
+ let start = (head === '->' || head === '=>') ? 2 : 1;
384
+ for (let i = start; i < node.length; i++) {
376
385
  if (Array.isArray(node[i])) this.collectSubExprs(node[i], result);
377
386
  }
378
387
  }
@@ -411,7 +420,7 @@ export class CodeGenerator {
411
420
  if (head === 'component') return; // Component body has its own scope
412
421
  if (head === 'enum') return; // Enum members are not top-level variables
413
422
 
414
- if (CodeGenerator.ASSIGNMENT_OPS.has(head)) {
423
+ if (CodeEmitter.ASSIGNMENT_OPS.has(head)) {
415
424
  let [target, value] = rest;
416
425
  if (typeof target === 'string' || target instanceof String) {
417
426
  let varName = str(target);
@@ -469,7 +478,7 @@ export class CodeGenerator {
469
478
  let [head, ...rest] = sexpr;
470
479
  head = str(head);
471
480
  if (Array.isArray(head)) { sexpr.forEach(item => collect(item)); return; }
472
- if (CodeGenerator.ASSIGNMENT_OPS.has(head)) {
481
+ if (CodeEmitter.ASSIGNMENT_OPS.has(head)) {
473
482
  let [target, value] = rest;
474
483
  if (typeof target === 'string') vars.add(target);
475
484
  else if (this.is(target, 'array')) this.collectVarsFromArray(target, vars);
@@ -505,7 +514,7 @@ export class CodeGenerator {
505
514
  // Main dispatch
506
515
  // ---------------------------------------------------------------------------
507
516
 
508
- generate(sexpr, context = 'statement') {
517
+ emit(sexpr, context = 'statement') {
509
518
  // String object with metadata (quote, await, predicate, heregex, etc.)
510
519
  if (sexpr instanceof String) {
511
520
  // Dammit operator (!)
@@ -566,7 +575,7 @@ export class CodeGenerator {
566
575
 
567
576
  if (typeof sexpr === 'number') return String(sexpr);
568
577
  if (sexpr === null || sexpr === undefined) return 'null';
569
- if (!Array.isArray(sexpr)) throw new Error(`Invalid s-expression: ${JSON.stringify(sexpr)}`);
578
+ if (!Array.isArray(sexpr)) this.error(`Invalid s-expression: ${JSON.stringify(sexpr)}`, sexpr);
570
579
 
571
580
  let [head, ...rest] = sexpr;
572
581
 
@@ -575,37 +584,24 @@ export class CodeGenerator {
575
584
  head = str(head);
576
585
 
577
586
  // Dispatch table
578
- let method = CodeGenerator.GENERATORS[head];
587
+ let method = CodeEmitter.GENERATORS[head];
579
588
  if (method) return this[method](head, rest, context, sexpr);
580
589
 
581
590
  // ---- Function calls (dynamic — not in dispatch table) ----
582
591
 
583
592
  if (typeof head === 'string' && !head.startsWith('"') && !head.startsWith("'")) {
584
- if (CodeGenerator.NUMBER_START_RE.test(head)) return head;
593
+ if (CodeEmitter.NUMBER_START_RE.test(head)) return head;
585
594
 
586
595
  // super.methodName() in non-constructor methods
587
596
  if (head === 'super' && this.currentMethodName && this.currentMethodName !== 'constructor') {
588
- let args = rest.map(arg => this.unwrap(this.generate(arg, 'value'))).join(', ');
589
- return `super.${this.currentMethodName}(${args})`;
590
- }
591
-
592
- // Postfix if on single-arg call
593
- if (context === 'statement' && rest.length === 1) {
594
- let cond = this.findPostfixConditional(rest[0]);
595
- if (cond) {
596
- let argWithout = this.rebuildWithoutConditional(cond);
597
- let callee = this.generate(head, 'value');
598
- let condCode = this.generate(cond.condition, 'value');
599
- let valCode = this.generate(argWithout, 'value');
600
- let callStr = `${callee}(${valCode})`;
601
- return `if (${condCode}) ${callStr}`;
602
- }
597
+ return `super.${this.currentMethodName}(${this._emitArgs(rest)})`;
603
598
  }
604
599
 
600
+ let postfix = this._tryPostfixCall(head, rest, context);
601
+ if (postfix) return postfix;
602
+
605
603
  let needsAwait = headAwaitMeta === true;
606
- let calleeName = this.generate(head, 'value');
607
- let args = rest.map(arg => this.unwrap(this.generate(arg, 'value'))).join(', ');
608
- let callStr = `${calleeName}(${args})`;
604
+ let callStr = `${this.emit(head, 'value')}(${this._emitArgs(rest)})`;
609
605
  return needsAwait ? `await ${callStr}` : callStr;
610
606
  }
611
607
 
@@ -613,8 +609,7 @@ export class CodeGenerator {
613
609
  if (Array.isArray(head) && typeof head[0] === 'string') {
614
610
  let stmtOps = ['=', '+=', '-=', '*=', '/=', '%=', '**=', '&&=', '||=', '??=', 'if', 'return', 'throw'];
615
611
  if (stmtOps.includes(head[0])) {
616
- let exprs = sexpr.map(stmt => this.generate(stmt, 'value'));
617
- return `(${exprs.join(', ')})`;
612
+ return `(${sexpr.map(stmt => this.emit(stmt, 'value')).join(', ')})`;
618
613
  }
619
614
  }
620
615
 
@@ -623,53 +618,41 @@ export class CodeGenerator {
623
618
  // Ruby-style: XXX.new(args) → new XXX(args)
624
619
  if (head[0] === '.' && (head[2] === 'new' || str(head[2]) === 'new')) {
625
620
  let ctorExpr = head[1];
626
- let ctorCode = this.generate(ctorExpr, 'value');
627
- let args = rest.map(arg => this.unwrap(this.generate(arg, 'value'))).join(', ');
621
+ let ctorCode = this.emit(ctorExpr, 'value');
628
622
  let needsParens = Array.isArray(ctorExpr);
629
- return `new ${needsParens ? `(${ctorCode})` : ctorCode}(${args})`;
630
- }
631
-
632
- // Postfix if on single-arg method call
633
- if (context === 'statement' && rest.length === 1) {
634
- let cond = this.findPostfixConditional(rest[0]);
635
- if (cond) {
636
- let argWithout = this.rebuildWithoutConditional(cond);
637
- let calleeCode = this.generate(head, 'value');
638
- let condCode = this.generate(cond.condition, 'value');
639
- let valCode = this.generate(argWithout, 'value');
640
- let callStr = `${calleeCode}(${valCode})`;
641
- return `if (${condCode}) ${callStr}`;
642
- }
623
+ return `new ${needsParens ? `(${ctorCode})` : ctorCode}(${this._emitArgs(rest)})`;
643
624
  }
644
625
 
626
+ let postfix = this._tryPostfixCall(head, rest, context);
627
+ if (postfix) return postfix;
628
+
645
629
  // Property access with await sigil on property
646
630
  let needsAwait = false;
647
631
  let calleeCode;
648
632
  if (head[0] === '.' && meta(head[2], 'await') === true) {
649
633
  needsAwait = true;
650
634
  let [obj, prop] = head.slice(1);
651
- let objCode = this.generate(obj, 'value');
652
- let needsParens = CodeGenerator.NUMBER_LITERAL_RE.test(objCode) ||
635
+ let objCode = this.emit(obj, 'value');
636
+ let needsParens = CodeEmitter.NUMBER_LITERAL_RE.test(objCode) ||
653
637
  ((this.is(obj, 'object') || this.is(obj, 'await') || this.is(obj, 'yield')));
654
638
  let base = needsParens ? `(${objCode})` : objCode;
655
639
  calleeCode = `${base}.${str(prop)}`;
656
640
  } else {
657
- calleeCode = this.generate(head, 'value');
641
+ calleeCode = this.emit(head, 'value');
658
642
  }
659
643
 
660
- let args = rest.map(arg => this.unwrap(this.generate(arg, 'value'))).join(', ');
661
- let callStr = `${calleeCode}(${args})`;
644
+ let callStr = `${calleeCode}(${this._emitArgs(rest)})`;
662
645
  return needsAwait ? `await ${callStr}` : callStr;
663
646
  }
664
647
 
665
- throw new Error(`Unknown s-expression type: ${head}`);
648
+ this.error(`Unknown s-expression type: ${head}`, sexpr);
666
649
  }
667
650
 
668
651
  // ---------------------------------------------------------------------------
669
652
  // Program
670
653
  // ---------------------------------------------------------------------------
671
654
 
672
- generateProgram(head, statements, context, sexpr) {
655
+ emitProgram(head, statements, context, sexpr) {
673
656
  let code = '';
674
657
  let imports = [], body = [];
675
658
 
@@ -680,6 +663,17 @@ export class CodeGenerator {
680
663
  else body.push(stmt);
681
664
  }
682
665
 
666
+ // Classify program-level variables for inline declarations
667
+ let prevInlinePending = this._inlineVarsPending;
668
+ let programInlineVars = new Set();
669
+ if (this.programVars.size > 0 && body.length > 0) {
670
+ let classified = this.classifyVarsForInlining(body, this.programVars);
671
+ programInlineVars = classified.inlineVars;
672
+ // '_' is always emitted as 'var _' (not 'let'), so must not be inlined
673
+ programInlineVars.delete('_');
674
+ if (programInlineVars.size > 0) this._inlineVarsPending = new Set(programInlineVars);
675
+ }
676
+
683
677
  // Generate body first to detect needed helpers
684
678
  let blockStmts = ['def', 'class', 'if', 'for-in', 'for-of', 'for-as', 'while', 'loop', 'switch', 'try'];
685
679
  let stmtEntries = body.map((stmt, index) => {
@@ -693,9 +687,9 @@ export class CodeGenerator {
693
687
  let isLastComp = isLast && isAlreadyExpr;
694
688
 
695
689
  let generated;
696
- if (needsParens) generated = `(${this.generate(stmt, 'value')})`;
697
- else if (isLastComp) generated = this.generate(stmt, 'value');
698
- else generated = this.generate(stmt, 'statement');
690
+ if (needsParens) generated = `(${this.emit(stmt, 'value')})`;
691
+ else if (isLastComp) generated = this.emit(stmt, 'value');
692
+ else generated = this.emit(stmt, 'statement');
699
693
 
700
694
  if (generated && !generated.endsWith(';')) {
701
695
  let h = Array.isArray(stmt) ? stmt[0] : null;
@@ -706,10 +700,12 @@ export class CodeGenerator {
706
700
  });
707
701
  let statementsCode = stmtEntries.map(e => e.code).join('\n');
708
702
 
703
+ this._inlineVarsPending = prevInlinePending;
704
+
709
705
  let needsBlank = false;
710
706
 
711
707
  if (imports.length > 0) {
712
- code += imports.map(s => this.addSemicolon(s, this.generate(s, 'statement'))).join('\n');
708
+ code += imports.map(s => this.addSemicolon(s, this.emit(s, 'statement'))).join('\n');
713
709
  needsBlank = true;
714
710
  }
715
711
 
@@ -717,10 +713,12 @@ export class CodeGenerator {
717
713
  let hasUnderscore = this.programVars.has('_');
718
714
  if (hasUnderscore) this.programVars.delete('_');
719
715
  if (this.programVars.size > 0) {
720
- let vars = Array.from(this.programVars).sort().join(', ');
721
- if (needsBlank) code += '\n';
722
- code += `let ${vars};\n`;
723
- needsBlank = true;
716
+ let vars = Array.from(this.programVars).filter(v => !programInlineVars.has(v)).sort().join(', ');
717
+ if (vars) {
718
+ if (needsBlank) code += '\n';
719
+ code += `let ${vars};\n`;
720
+ needsBlank = true;
721
+ }
724
722
  }
725
723
  if (hasUnderscore) {
726
724
  if (needsBlank) code += '\n';
@@ -806,16 +804,16 @@ export class CodeGenerator {
806
804
  // Binary operators
807
805
  // ---------------------------------------------------------------------------
808
806
 
809
- generateBinaryOp(op, rest, context, sexpr) {
807
+ emitBinaryOp(op, rest, context, sexpr) {
810
808
  if ((op === '+' || op === '-') && rest.length === 1) {
811
- return `(${op}${this.generate(rest[0], 'value')})`;
809
+ return `(${op}${this.emit(rest[0], 'value')})`;
812
810
  }
813
811
  let [left, right] = rest;
814
812
  // String repeat: "str" * n → "str".repeat(n)
815
813
  if (op === '*') {
816
814
  let leftStr = left?.valueOf?.() ?? left;
817
815
  if (typeof leftStr === 'string' && /^["']/.test(leftStr)) {
818
- return `${this.generate(left, 'value')}.repeat(${this.generate(right, 'value')})`;
816
+ return `${this.emit(left, 'value')}.repeat(${this.emit(right, 'value')})`;
819
817
  }
820
818
  }
821
819
  // Chained comparisons: (< (< a b) c) → ((a < b) && (b < c))
@@ -823,42 +821,38 @@ export class CodeGenerator {
823
821
  if (COMPARE_OPS.has(op) && Array.isArray(left)) {
824
822
  let leftOp = left[0]?.valueOf?.() ?? left[0];
825
823
  if (COMPARE_OPS.has(leftOp)) {
826
- let a = this.generate(left[1], 'value');
827
- let b = this.generate(left[2], 'value');
828
- let c = this.generate(right, 'value');
824
+ let a = this.emit(left[1], 'value');
825
+ let b = this.emit(left[2], 'value');
826
+ let c = this.emit(right, 'value');
829
827
  return `((${a} ${leftOp} ${b}) && (${b} ${op} ${c}))`;
830
828
  }
831
829
  }
832
- if (op === '!?') {
833
- let l = this.generate(left, 'value'), r = this.generate(right, 'value');
834
- return `(${l} !== undefined ? ${l} : ${r})`;
835
- }
836
830
  if (op === '==') op = '===';
837
831
  if (op === '!=') op = '!==';
838
- return `(${this.generate(left, 'value')} ${op} ${this.generate(right, 'value')})`;
832
+ return `(${this.emit(left, 'value')} ${op} ${this.emit(right, 'value')})`;
839
833
  }
840
834
 
841
- generateModulo(head, rest) {
835
+ emitModulo(head, rest) {
842
836
  let [left, right] = rest;
843
837
  this.helpers.add('modulo');
844
- return `modulo(${this.generate(left, 'value')}, ${this.generate(right, 'value')})`;
838
+ return `modulo(${this.emit(left, 'value')}, ${this.emit(right, 'value')})`;
845
839
  }
846
840
 
847
- generateModuloAssign(head, rest) {
841
+ emitModuloAssign(head, rest) {
848
842
  let [target, value] = rest;
849
843
  this.helpers.add('modulo');
850
- let t = this.generate(target, 'value'), v = this.generate(value, 'value');
844
+ let t = this.emit(target, 'value'), v = this.emit(value, 'value');
851
845
  return `${t} = modulo(${t}, ${v})`;
852
846
  }
853
847
 
854
- generateFloorDiv(head, rest) {
848
+ emitFloorDiv(head, rest) {
855
849
  let [left, right] = rest;
856
- return `Math.floor(${this.generate(left, 'value')} / ${this.generate(right, 'value')})`;
850
+ return `Math.floor(${this.emit(left, 'value')} / ${this.emit(right, 'value')})`;
857
851
  }
858
852
 
859
- generateFloorDivAssign(head, rest) {
853
+ emitFloorDivAssign(head, rest) {
860
854
  let [target, value] = rest;
861
- let t = this.generate(target, 'value'), v = this.generate(value, 'value');
855
+ let t = this.emit(target, 'value'), v = this.emit(value, 'value');
862
856
  return `${t} = Math.floor(${t} / ${v})`;
863
857
  }
864
858
 
@@ -866,16 +860,16 @@ export class CodeGenerator {
866
860
  // Assignment
867
861
  // ---------------------------------------------------------------------------
868
862
 
869
- generateAssignment(head, rest, context, sexpr) {
863
+ emitAssignment(head, rest, context, sexpr) {
870
864
  let [target, value] = rest;
871
865
  let op = head === '?=' ? '??=' : head;
872
866
 
873
867
  // Optional chain assignment: x?.prop = val → if (x != null) x.prop = val
874
868
  let optInfo = this._findOptionalInTarget(target);
875
869
  if (optInfo) {
876
- let guardCode = this.generate(optInfo.guard, 'value');
877
- let targetCode = this.generate(optInfo.rewritten, 'value');
878
- let valueCode = this.generate(value, 'value');
870
+ let guardCode = this.emit(optInfo.guard, 'value');
871
+ let targetCode = this.emit(optInfo.rewritten, 'value');
872
+ let valueCode = this.emit(value, 'value');
879
873
  if (context === 'value') {
880
874
  return `(${guardCode} != null ? (${targetCode} ${op} ${valueCode}) : undefined)`;
881
875
  }
@@ -886,7 +880,7 @@ export class CodeGenerator {
886
880
  let isFnValue = (this.is(value, '->') || this.is(value, '=>') || this.is(value, 'def'));
887
881
  if (target instanceof String && meta(target, 'await') !== undefined && !isFnValue) {
888
882
  let sigil = meta(target, 'await') === true ? '!' : '&';
889
- throw new Error(`Cannot use ${sigil} sigil in variable declaration '${str(target)}'.`);
883
+ this.error(`Cannot use ${sigil} sigil in variable declaration '${str(target)}'`, sexpr);
890
884
  }
891
885
 
892
886
  if (target instanceof String && meta(target, 'await') === true && isFnValue) {
@@ -897,7 +891,7 @@ export class CodeGenerator {
897
891
  let isEmptyArr = this.is(target, 'array', 0);
898
892
  let isEmptyObj = this.is(target, 'object', 0);
899
893
  if (isEmptyArr || isEmptyObj) {
900
- let v = this.generate(value, 'value');
894
+ let v = this.emit(value, 'value');
901
895
  return (isEmptyObj && context === 'statement') ? `(${v})` : v;
902
896
  }
903
897
 
@@ -906,17 +900,25 @@ export class CodeGenerator {
906
900
  let [, rawCtrlOp, expr, ctrlSexpr] = value;
907
901
  let ctrlOp = str(rawCtrlOp);
908
902
  let isReturn = ctrlSexpr[0] === 'return';
909
- let targetCode = this.generate(target, 'value');
910
- let exprCode = this.generate(expr, 'value');
903
+ let targetCode = this.emit(target, 'value');
904
+ let exprCode = this.emit(expr, 'value');
911
905
  let ctrlValue = ctrlSexpr.length > 1 ? ctrlSexpr[1] : null;
912
906
  let ctrlCode = isReturn
913
- ? (ctrlValue ? `return ${this.generate(ctrlValue, 'value')}` : 'return')
914
- : (ctrlValue ? `throw ${this.generate(ctrlValue, 'value')}` : 'throw new Error()');
907
+ ? (ctrlValue ? `return ${this.emit(ctrlValue, 'value')}` : 'return')
908
+ : (ctrlValue ? `throw ${this.emit(ctrlValue, 'value')}` : 'throw new Error()');
915
909
  if (context === 'value') {
916
910
  if (ctrlOp === '??') return `(() => { const __v = ${exprCode}; if (__v == null) ${ctrlCode}; return (${targetCode} = __v); })()`;
917
911
  if (ctrlOp === '||') return `(() => { const __v = ${exprCode}; if (!__v) ${ctrlCode}; return (${targetCode} = __v); })()`;
918
912
  return `(() => { const __v = ${exprCode}; if (__v) ${ctrlCode}; return (${targetCode} = __v); })()`;
919
913
  }
914
+ // Inline `let` for control flow — split into declaration + guard
915
+ let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
916
+ if (tgtName && context === 'statement' && this._inlineVarsPending?.delete(tgtName)) {
917
+ let ind = this.indent();
918
+ if (ctrlOp === '??') return `let ${targetCode} = ${exprCode};\n${ind}if (${targetCode} == null) ${ctrlCode}`;
919
+ if (ctrlOp === '||') return `let ${targetCode} = ${exprCode};\n${ind}if (!${targetCode}) ${ctrlCode}`;
920
+ return `let ${targetCode} = ${exprCode};\n${ind}if (${targetCode}) ${ctrlCode}`;
921
+ }
920
922
  if (ctrlOp === '??') return `if ((${targetCode} = ${exprCode}) == null) ${ctrlCode}`;
921
923
  if (ctrlOp === '||') return `if (!(${targetCode} = ${exprCode})) ${ctrlCode}`;
922
924
  return `if ((${targetCode} = ${exprCode})) ${ctrlCode}`;
@@ -930,10 +932,10 @@ export class CodeGenerator {
930
932
  let afterRest = elements.slice(restIdx + 1);
931
933
  let afterCount = afterRest.length;
932
934
  if (afterCount > 0) {
933
- let valueCode = this.generate(value, 'value');
935
+ let valueCode = this.emit(value, 'value');
934
936
  let beforeRest = elements.slice(0, restIdx);
935
- let beforePattern = beforeRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.generate(el, 'value')).join(', ');
936
- let afterPattern = afterRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.generate(el, 'value')).join(', ');
937
+ let beforePattern = beforeRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.emit(el, 'value')).join(', ');
938
+ let afterPattern = afterRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.emit(el, 'value')).join(', ');
937
939
  this.helpers.add('slice');
938
940
  elements.forEach(el => {
939
941
  if (el === ',' || el === '...') return;
@@ -959,7 +961,9 @@ export class CodeGenerator {
959
961
  let [, condition, wrappedValue] = right;
960
962
  let unwrapped = Array.isArray(wrappedValue) && wrappedValue.length === 1 ? wrappedValue[0] : wrappedValue;
961
963
  let fullValue = [binOp, left, unwrapped];
962
- let t = this.generate(target, 'value'), c = this.generate(condition, 'value'), v = this.generate(fullValue, 'value');
964
+ let t = this.emit(target, 'value'), c = this.emit(condition, 'value'), v = this.emit(fullValue, 'value');
965
+ let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
966
+ if (tgtName && this._inlineVarsPending?.delete(tgtName)) return `let ${t};\n${this.indent()}if (${c}) ${t} = ${v}`;
963
967
  return `if (${c}) ${t} = ${v}`;
964
968
  }
965
969
  }
@@ -971,9 +975,11 @@ export class CodeGenerator {
971
975
  (!Array.isArray(actualValue[0]) || actualValue[0][0] !== 'block');
972
976
  if (valHead === 'if' && isPostfix) {
973
977
  let unwrapped = Array.isArray(actualValue) && actualValue.length === 1 ? actualValue[0] : actualValue;
974
- let t = this.generate(target, 'value');
975
- let condCode = this.unwrapLogical(this.generate(condition, 'value'));
976
- let v = this.generate(unwrapped, 'value');
978
+ let t = this.emit(target, 'value');
979
+ let condCode = this.unwrapLogical(this.emit(condition, 'value'));
980
+ let v = this.emit(unwrapped, 'value');
981
+ let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
982
+ if (tgtName && this._inlineVarsPending?.delete(tgtName)) return `let ${t};\n${this.indent()}if (${condCode}) ${t} = ${v}`;
977
983
  return `if (${condCode}) ${t} = ${v}`;
978
984
  }
979
985
  }
@@ -985,16 +991,27 @@ export class CodeGenerator {
985
991
  } else if (typeof target === 'string' && this.reactiveVars?.has(target)) {
986
992
  targetCode = `${target}.value`;
987
993
  } else {
988
- targetCode = this.generate(target, 'value');
994
+ targetCode = this.emit(target, 'value');
989
995
  }
990
996
 
991
997
  const prevComponentName = this._componentName;
992
- if (this.is(value, 'component') && (typeof target === 'string' || target instanceof String)) this._componentName = str(target);
993
- let valueCode = this.generate(value, 'value');
998
+ const prevComponentTypeParams = this._componentTypeParams;
999
+ if (this.is(value, 'component') && (typeof target === 'string' || target instanceof String)) {
1000
+ this._componentName = str(target);
1001
+ this._componentTypeParams = target.typeParams || '';
1002
+ }
1003
+ let valueCode = this.emit(value, 'value');
994
1004
  this._componentName = prevComponentName;
1005
+ this._componentTypeParams = prevComponentTypeParams;
995
1006
  let isObjLit = this.is(value, 'object');
996
1007
  if (!isObjLit) valueCode = this.unwrap(valueCode);
997
1008
 
1009
+ // Inline `let` at first assignment for variables classified as inlinable
1010
+ let targetName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
1011
+ if (head === '=' && targetName && context === 'statement' && this._inlineVarsPending?.delete(targetName)) {
1012
+ return `let ${targetCode} = ${valueCode}`;
1013
+ }
1014
+
998
1015
  let needsParensVal = context === 'value';
999
1016
  let needsParensObj = context === 'statement' && this.is(target, 'object');
1000
1017
  if (needsParensVal || needsParensObj) return `(${targetCode} ${op} ${valueCode})`;
@@ -1005,15 +1022,15 @@ export class CodeGenerator {
1005
1022
  // Property access
1006
1023
  // ---------------------------------------------------------------------------
1007
1024
 
1008
- generatePropertyAccess(head, rest, context, sexpr) {
1025
+ emitPropertyAccess(head, rest, context, sexpr) {
1009
1026
  let [obj, prop] = rest;
1010
1027
  // In subclass constructors, rewrite @param refs (this.x) to _x for super() safety
1011
1028
  if (this._atParamMap && obj === 'this') {
1012
1029
  let mapped = this._atParamMap.get(str(prop));
1013
1030
  if (mapped) return mapped;
1014
1031
  }
1015
- let objCode = this.generate(obj, 'value');
1016
- let needsParens = CodeGenerator.NUMBER_LITERAL_RE.test(objCode) ||
1032
+ let objCode = this.emit(obj, 'value');
1033
+ let needsParens = CodeEmitter.NUMBER_LITERAL_RE.test(objCode) ||
1017
1034
  objCode.startsWith('await ') ||
1018
1035
  ((this.is(obj, 'object') || this.is(obj, 'yield')));
1019
1036
  let base = needsParens ? `(${objCode})` : objCode;
@@ -1022,26 +1039,26 @@ export class CodeGenerator {
1022
1039
  return `${base}.${str(prop)}`;
1023
1040
  }
1024
1041
 
1025
- generateOptionalProperty(head, rest) {
1042
+ emitOptionalProperty(head, rest) {
1026
1043
  let [obj, prop] = rest;
1027
- return `${this.generate(obj, 'value')}?.${prop}`;
1044
+ return `${this.emit(obj, 'value')}?.${prop}`;
1028
1045
  }
1029
1046
 
1030
- generateRegexIndex(head, rest) {
1047
+ emitRegexIndex(head, rest) {
1031
1048
  let [value, regex, captureIndex] = rest;
1032
1049
  this.helpers.add('toMatchable');
1033
1050
  this.programVars.add('_');
1034
- let v = this.generate(value, 'value'), r = this.generate(regex, 'value');
1035
- let idx = captureIndex !== null ? this.generate(captureIndex, 'value') : '0';
1051
+ let v = this.emit(value, 'value'), r = this.emit(regex, 'value');
1052
+ let idx = captureIndex !== null ? this.emit(captureIndex, 'value') : '0';
1036
1053
  let allowNL = r.includes('/m') ? ', true' : '';
1037
1054
  return `(_ = toMatchable(${v}${allowNL}).match(${r})) && _[${idx}]`;
1038
1055
  }
1039
1056
 
1040
- generateIndexAccess(head, rest) {
1057
+ emitIndexAccess(head, rest) {
1041
1058
  let [arr, index] = rest;
1042
1059
  if ((this.is(index, '..') || this.is(index, '...'))) {
1043
1060
  let isIncl = index[0] === '..';
1044
- let arrCode = this.generate(arr, 'value');
1061
+ let arrCode = this.emit(arr, 'value');
1045
1062
  let [start, end] = index.slice(1);
1046
1063
 
1047
1064
  // Detect compile-time numeric literals (positive, negative, String objects)
@@ -1067,76 +1084,76 @@ export class CodeGenerator {
1067
1084
  if (start === null && end === null) return `${arrCode}.slice()`;
1068
1085
  if (start === null) {
1069
1086
  if (isIncl && this.is(end, '-', 1) && (str(end[1]) ?? end[1]) == 1) return `${arrCode}.slice(0)`;
1070
- let e = this.generate(end, 'value');
1087
+ let e = this.emit(end, 'value');
1071
1088
  return isIncl ? inclEnd('0', e, end) : `${arrCode}.slice(0, ${e})`;
1072
1089
  }
1073
- if (end === null) return `${arrCode}.slice(${this.generate(start, 'value')})`;
1074
- let s = this.generate(start, 'value');
1090
+ if (end === null) return `${arrCode}.slice(${this.emit(start, 'value')})`;
1091
+ let s = this.emit(start, 'value');
1075
1092
  if (isIncl && this.is(end, '-', 1) && (str(end[1]) ?? end[1]) == 1) return `${arrCode}.slice(${s})`;
1076
- let e = this.generate(end, 'value');
1093
+ let e = this.emit(end, 'value');
1077
1094
  return isIncl ? inclEnd(s, e, end) : `${arrCode}.slice(${s}, ${e})`;
1078
1095
  }
1079
1096
  // Negative literal index: arr[-1] → arr.at(-1)
1080
1097
  if (this.is(index, '-', 1)) {
1081
1098
  let n = str(index[1]) ?? index[1];
1082
1099
  if (typeof n === 'number' || (typeof n === 'string' && /^\d+$/.test(n))) {
1083
- return `${this.generate(arr, 'value')}.at(-${n})`;
1100
+ return `${this.emit(arr, 'value')}.at(-${n})`;
1084
1101
  }
1085
1102
  }
1086
- return `${this.generate(arr, 'value')}[${this.unwrap(this.generate(index, 'value'))}]`;
1103
+ return `${this.emit(arr, 'value')}[${this.unwrap(this.emit(index, 'value'))}]`;
1087
1104
  }
1088
1105
 
1089
- generateOptIndex(head, rest) {
1106
+ emitOptIndex(head, rest) {
1090
1107
  let [arr, index] = rest;
1091
1108
  // Negative literal index: arr?[-1] → arr?.at(-1)
1092
1109
  if (this.is(index, '-', 1)) {
1093
1110
  let n = str(index[1]) ?? index[1];
1094
1111
  if (typeof n === 'number' || (typeof n === 'string' && /^\d+$/.test(n))) {
1095
- return `${this.generate(arr, 'value')}?.at(-${n})`;
1112
+ return `${this.emit(arr, 'value')}?.at(-${n})`;
1096
1113
  }
1097
1114
  }
1098
- return `${this.generate(arr, 'value')}?.[${this.generate(index, 'value')}]`;
1115
+ return `${this.emit(arr, 'value')}?.[${this.emit(index, 'value')}]`;
1099
1116
  }
1100
1117
 
1101
- generateOptCall(head, rest) {
1118
+ emitOptCall(head, rest) {
1102
1119
  let [fn, ...args] = rest;
1103
- return `${this.generate(fn, 'value')}?.(${args.map(a => this.generate(a, 'value')).join(', ')})`;
1120
+ return `${this.emit(fn, 'value')}?.(${args.map(a => this.emit(a, 'value')).join(', ')})`;
1104
1121
  }
1105
1122
 
1106
1123
  // ---------------------------------------------------------------------------
1107
1124
  // Functions
1108
1125
  // ---------------------------------------------------------------------------
1109
1126
 
1110
- generateDef(head, rest, context, sexpr) {
1127
+ emitDef(head, rest, context, sexpr) {
1111
1128
  let [name, params, body] = rest;
1112
1129
  let sideEffectOnly = meta(name, 'await') === true;
1113
1130
  let cleanName = str(name);
1114
- let paramList = this.generateParamList(params);
1115
- let bodyCode = this.generateFunctionBody(body, params, sideEffectOnly);
1131
+ let paramList = this.emitParamList(params);
1132
+ let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
1116
1133
  let isAsync = this.containsAwait(body);
1117
1134
  let isGen = this.containsYield(body);
1118
1135
  return `${isAsync ? 'async ' : ''}function${isGen ? '*' : ''} ${cleanName}(${paramList}) ${bodyCode}`;
1119
1136
  }
1120
1137
 
1121
- generateThinArrow(head, rest, context, sexpr) {
1138
+ emitThinArrow(head, rest, context, sexpr) {
1122
1139
  let [params, body] = rest;
1123
1140
  if ((!params || (Array.isArray(params) && params.length === 0)) && this.containsIt(body)) params = ['it'];
1124
1141
  let sideEffectOnly = this.nextFunctionIsVoid || false;
1125
1142
  this.nextFunctionIsVoid = false;
1126
- let paramList = this.generateParamList(params);
1127
- let bodyCode = this.generateFunctionBody(body, params, sideEffectOnly);
1143
+ let paramList = this.emitParamList(params);
1144
+ let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
1128
1145
  let isAsync = this.containsAwait(body);
1129
1146
  let isGen = this.containsYield(body);
1130
1147
  let fn = `${isAsync ? 'async ' : ''}function${isGen ? '*' : ''}(${paramList}) ${bodyCode}`;
1131
1148
  return context === 'value' ? `(${fn})` : fn;
1132
1149
  }
1133
1150
 
1134
- generateFatArrow(head, rest, context, sexpr) {
1151
+ emitFatArrow(head, rest, context, sexpr) {
1135
1152
  let [params, body] = rest;
1136
1153
  if ((!params || (Array.isArray(params) && params.length === 0)) && this.containsIt(body)) params = ['it'];
1137
1154
  let sideEffectOnly = this.nextFunctionIsVoid || false;
1138
1155
  this.nextFunctionIsVoid = false;
1139
- let paramList = this.generateParamList(params);
1156
+ let paramList = this.emitParamList(params);
1140
1157
  let isSingle = params.length === 1 && typeof params[0] === 'string' &&
1141
1158
  !paramList.includes('=') && !paramList.includes('...') &&
1142
1159
  !paramList.includes('[') && !paramList.includes('{');
@@ -1149,89 +1166,89 @@ export class CodeGenerator {
1149
1166
  let expr = body[1];
1150
1167
  let exprHead = Array.isArray(expr) ? expr[0] : null;
1151
1168
  if (exprHead !== 'return' && !STMT_ONLY.has(exprHead)) {
1152
- let code = this.generate(expr, 'value');
1169
+ let code = this.emit(expr, 'value');
1153
1170
  if (code[0] === '{') code = `(${code})`;
1154
1171
  return `${prefix}${paramSyntax} => ${code}`;
1155
1172
  }
1156
1173
  }
1157
1174
  if (!Array.isArray(body) || body[0] !== 'block') {
1158
- let code = this.generate(body, 'value');
1175
+ let code = this.emit(body, 'value');
1159
1176
  if (code[0] === '{') code = `(${code})`;
1160
1177
  return `${prefix}${paramSyntax} => ${code}`;
1161
1178
  }
1162
1179
  }
1163
1180
 
1164
- let bodyCode = this.generateFunctionBody(body, params, sideEffectOnly);
1181
+ let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
1165
1182
  return `${prefix}${paramSyntax} => ${bodyCode}`;
1166
1183
  }
1167
1184
 
1168
- generateReturn(head, rest, context, sexpr) {
1185
+ emitReturn(head, rest, context, sexpr) {
1169
1186
  if (rest.length === 0) return 'return';
1170
1187
  let [expr] = rest;
1171
1188
  if (this.sideEffectOnly && !(this.is(expr, '->') || this.is(expr, '=>'))) {
1172
- throw new Error(`Cannot return a value from a void function (declared with !)`);
1189
+ this.error('Cannot return a value from a void function (declared with !)', sexpr);
1173
1190
  }
1174
1191
 
1175
1192
  if (this.is(expr, 'if')) {
1176
1193
  let [, condition, body, ...elseParts] = expr;
1177
1194
  if (elseParts.length === 0) {
1178
1195
  let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
1179
- return `if (${this.generate(condition, 'value')}) return ${this.generate(val, 'value')}`;
1196
+ return `if (${this.emit(condition, 'value')}) return ${this.emit(val, 'value')}`;
1180
1197
  }
1181
1198
  }
1182
1199
  if (this.is(expr, 'new') && Array.isArray(expr[1]) && expr[1][0] === 'if') {
1183
1200
  let [, condition, body] = expr[1];
1184
1201
  let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
1185
- return `if (${this.generate(condition, 'value')}) return ${this.generate(['new', val], 'value')}`;
1202
+ return `if (${this.emit(condition, 'value')}) return ${this.emit(['new', val], 'value')}`;
1186
1203
  }
1187
- return `return ${this.generate(expr, 'value')}`;
1204
+ return `return ${this.emit(expr, 'value')}`;
1188
1205
  }
1189
1206
 
1190
1207
  // ---------------------------------------------------------------------------
1191
1208
  // Reactive
1192
1209
  // ---------------------------------------------------------------------------
1193
1210
 
1194
- generateState(head, rest) {
1211
+ emitState(head, rest) {
1195
1212
  let [name, expr] = rest;
1196
1213
  this.usesReactivity = true;
1197
1214
  let varName = str(name) ?? name;
1198
1215
  if (!this.reactiveVars) this.reactiveVars = new Set();
1199
1216
  this.reactiveVars.add(varName);
1200
- return `const ${varName} = __state(${this.generate(expr, 'value')})`;
1217
+ return `const ${varName} = __state(${this.emit(expr, 'value')})`;
1201
1218
  }
1202
1219
 
1203
- generateComputed(head, rest) {
1220
+ emitComputed(head, rest) {
1204
1221
  let [name, expr] = rest;
1205
1222
  this.usesReactivity = true;
1206
1223
  if (!this.reactiveVars) this.reactiveVars = new Set();
1207
1224
  let varName = str(name) ?? name;
1208
1225
  this.reactiveVars.add(varName);
1209
1226
  if (this.is(expr, 'block') && expr.length > 2) {
1210
- return `const ${varName} = __computed(() => ${this.generateFunctionBody(expr)})`;
1227
+ return `const ${varName} = __computed(() => ${this.emitFunctionBody(expr)})`;
1211
1228
  }
1212
- return `const ${varName} = __computed(() => ${this.generate(expr, 'value')})`;
1229
+ return `const ${varName} = __computed(() => ${this.emit(expr, 'value')})`;
1213
1230
  }
1214
1231
 
1215
- generateReadonly(head, rest) {
1232
+ emitReadonly(head, rest) {
1216
1233
  let [name, expr] = rest;
1217
- return `const ${str(name) ?? name} = ${this.generate(expr, 'value')}`;
1234
+ return `const ${str(name) ?? name} = ${this.emit(expr, 'value')}`;
1218
1235
  }
1219
1236
 
1220
- generateEffect(head, rest) {
1237
+ emitEffect(head, rest) {
1221
1238
  let [target, body] = rest;
1222
1239
  this.usesReactivity = true;
1223
1240
  let bodyCode;
1224
1241
  if (this.is(body, 'block')) {
1225
- bodyCode = this.generateFunctionBody(body);
1242
+ bodyCode = this.emitFunctionBody(body);
1226
1243
  } else if ((this.is(body, '->') || this.is(body, '=>'))) {
1227
- let fnCode = this.generate(body, 'value');
1228
- if (target) return `const ${str(target) ?? this.generate(target, 'value')} = __effect(${fnCode})`;
1244
+ let fnCode = this.emit(body, 'value');
1245
+ if (target) return `const ${str(target) ?? this.emit(target, 'value')} = __effect(${fnCode})`;
1229
1246
  return `__effect(${fnCode})`;
1230
1247
  } else {
1231
- bodyCode = `{ ${this.generate(body, 'value')}; }`;
1248
+ bodyCode = `{ ${this.emit(body, 'value')}; }`;
1232
1249
  }
1233
1250
  let effectCode = `__effect(() => ${bodyCode})`;
1234
- if (target) return `const ${str(target) ?? this.generate(target, 'value')} = ${effectCode}`;
1251
+ if (target) return `const ${str(target) ?? this.emit(target, 'value')} = ${effectCode}`;
1235
1252
  return effectCode;
1236
1253
  }
1237
1254
 
@@ -1239,106 +1256,102 @@ export class CodeGenerator {
1239
1256
  // Control flow — simple
1240
1257
  // ---------------------------------------------------------------------------
1241
1258
 
1242
- generateBreak() { return 'break'; }
1243
- generateContinue() { return 'continue'; }
1244
-
1245
- generateExistential(head, rest) {
1246
- return `(${this.generate(rest[0], 'value')} != null)`;
1247
- }
1259
+ emitBreak() { return 'break'; }
1260
+ emitContinue() { return 'continue'; }
1248
1261
 
1249
- generateDefined(head, rest) {
1250
- return `(${this.generate(rest[0], 'value')} !== undefined)`;
1262
+ emitExistential(head, rest) {
1263
+ return `(${this.emit(rest[0], 'value')} != null)`;
1251
1264
  }
1252
1265
 
1253
- generatePresence(head, rest) {
1254
- return `(${this.generate(rest[0], 'value')} ? true : undefined)`;
1266
+ emitPresence(head, rest) {
1267
+ return `(${this.emit(rest[0], 'value')} ? true : undefined)`;
1255
1268
  }
1256
1269
 
1257
- generateTernary(head, rest, context) {
1270
+ emitTernary(head, rest, context) {
1258
1271
  let [cond, then_, else_] = rest;
1259
1272
 
1260
1273
  // Hoist assignment: (cond ? (x = a) : b) → x = (cond ? a : b)
1261
1274
  // Enables: x = "admin" if cond else "member" without parens
1262
1275
  let thenHead = then_?.[0]?.valueOf?.() ?? then_?.[0];
1263
1276
  if (thenHead === '=' && Array.isArray(then_)) {
1264
- let target = this.generate(then_[1], 'value');
1265
- let thenVal = this.generate(then_[2], 'value');
1266
- let elseVal = this.generate(else_, 'value');
1267
- return `${target} = (${this.unwrap(this.generate(cond, 'value'))} ? ${thenVal} : ${elseVal})`;
1277
+ let target = this.emit(then_[1], 'value');
1278
+ let thenVal = this.emit(then_[2], 'value');
1279
+ let elseVal = this.emit(else_, 'value');
1280
+ return `${target} = (${this.unwrap(this.emit(cond, 'value'))} ? ${thenVal} : ${elseVal})`;
1268
1281
  }
1269
1282
 
1270
- return `(${this.unwrap(this.generate(cond, 'value'))} ? ${this.generate(then_, 'value')} : ${this.generate(else_, 'value')})`;
1283
+ return `(${this.unwrap(this.emit(cond, 'value'))} ? ${this.emit(then_, 'value')} : ${this.emit(else_, 'value')})`;
1271
1284
  }
1272
1285
 
1273
- generatePipe(head, rest) {
1286
+ emitPipe(head, rest) {
1274
1287
  let [left, right] = rest;
1275
- let leftCode = this.generate(left, 'value');
1288
+ let leftCode = this.emit(left, 'value');
1276
1289
  // Detect function calls: [fn, ...args] where fn is an identifier or accessor
1277
1290
  if (Array.isArray(right) && right.length > 1) {
1278
1291
  let fn = right[0];
1279
1292
  let isCall = Array.isArray(fn) || (typeof fn === 'string' && /^[a-zA-Z_$]/.test(fn));
1280
1293
  if (isCall) {
1281
- let fnCode = this.generate(fn, 'value');
1282
- let args = right.slice(1).map(a => this.generate(a, 'value'));
1294
+ let fnCode = this.emit(fn, 'value');
1295
+ let args = right.slice(1).map(a => this.emit(a, 'value'));
1283
1296
  return `${fnCode}(${leftCode}, ${args.join(', ')})`;
1284
1297
  }
1285
1298
  }
1286
1299
  // Simple reference or property access — call with left as sole arg
1287
- return `${this.generate(right, 'value')}(${leftCode})`;
1300
+ return `${this.emit(right, 'value')}(${leftCode})`;
1288
1301
  }
1289
1302
 
1290
- generateLoop(head, rest) {
1291
- return `while (true) ${this.generateLoopBody(rest[0])}`;
1303
+ emitLoop(head, rest) {
1304
+ return `while (true) ${this.emitLoopBody(rest[0])}`;
1292
1305
  }
1293
1306
 
1294
- generateLoopN(head, rest) {
1307
+ emitLoopN(head, rest) {
1295
1308
  let [count, body] = rest;
1296
- let n = this.generate(count, 'value');
1297
- return `for (let it = 0; it < ${n}; it++) ${this.generateLoopBody(body)}`;
1309
+ let n = this.emit(count, 'value');
1310
+ return `for (let it = 0; it < ${n}; it++) ${this.emitLoopBody(body)}`;
1298
1311
  }
1299
1312
 
1300
- generateAwait(head, rest) { return `await ${this.generate(rest[0], 'value')}`; }
1313
+ emitAwait(head, rest) { return `await ${this.emit(rest[0], 'value')}`; }
1301
1314
 
1302
- generateYield(head, rest) {
1303
- return rest.length === 0 ? 'yield' : `yield ${this.generate(rest[0], 'value')}`;
1315
+ emitYield(head, rest) {
1316
+ return rest.length === 0 ? 'yield' : `yield ${this.emit(rest[0], 'value')}`;
1304
1317
  }
1305
1318
 
1306
- generateYieldFrom(head, rest) { return `yield* ${this.generate(rest[0], 'value')}`; }
1319
+ emitYieldFrom(head, rest) { return `yield* ${this.emit(rest[0], 'value')}`; }
1307
1320
 
1308
1321
  // ---------------------------------------------------------------------------
1309
1322
  // Conditionals
1310
1323
  // ---------------------------------------------------------------------------
1311
1324
 
1312
- generateIf(head, rest, context, sexpr) {
1325
+ emitIf(head, rest, context, sexpr) {
1313
1326
  let [condition, thenBranch, ...elseBranches] = rest;
1314
1327
  return context === 'value'
1315
- ? this.generateIfAsExpression(condition, thenBranch, elseBranches)
1316
- : this.generateIfAsStatement(condition, thenBranch, elseBranches);
1328
+ ? this.emitIfAsExpression(condition, thenBranch, elseBranches)
1329
+ : this.emitIfAsStatement(condition, thenBranch, elseBranches);
1317
1330
  }
1318
1331
 
1319
1332
  // ---------------------------------------------------------------------------
1320
1333
  // Loops
1321
1334
  // ---------------------------------------------------------------------------
1322
1335
 
1323
- generateForIn(head, rest, context, sexpr) {
1336
+ emitForIn(head, rest, context, sexpr) {
1324
1337
  let [vars, iterable, step, guard, body] = rest;
1325
1338
 
1326
1339
  if (context === 'value' && this.comprehensionDepth === 0) {
1327
1340
  let iterator = ['for-in', vars, iterable, step];
1328
- return this.generate(['comprehension', body, [iterator], guard ? [guard] : []], context);
1341
+ return this.emit(['comprehension', body, [iterator], guard ? [guard] : []], context);
1329
1342
  }
1330
1343
 
1331
1344
  let varsArray = Array.isArray(vars) ? vars : [vars];
1332
1345
  let noVar = varsArray.length === 0;
1333
1346
  let [itemVar, indexVar] = noVar ? ['_i', null] : varsArray;
1334
1347
  let itemVarPattern = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
1335
- ? this.generateDestructuringPattern(itemVar) : itemVar;
1348
+ ? this.emitDestructuringPattern(itemVar) : itemVar;
1336
1349
 
1337
1350
  // Stepped iteration
1338
1351
  if (step && step !== null) {
1339
- let iterCode = this.generate(iterable, 'value');
1352
+ let iterCode = this.emit(iterable, 'value');
1340
1353
  let idxName = indexVar || '_i';
1341
- let stepCode = this.generate(step, 'value');
1354
+ let stepCode = this.emit(step, 'value');
1342
1355
  let isNeg = this.is(step, '-', 1);
1343
1356
  let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
1344
1357
  let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
@@ -1355,13 +1368,13 @@ export class CodeGenerator {
1355
1368
  let lines = [];
1356
1369
  if (!noVar) lines.push(`const ${itemVarPattern} = ${iterCode}[${idxName}];`);
1357
1370
  if (guard) {
1358
- lines.push(`if (${this.generate(guard, 'value')}) {`);
1371
+ lines.push(`if (${this.emit(guard, 'value')}) {`);
1359
1372
  this.indentLevel++;
1360
1373
  lines.push(...this.formatStatements(stmts));
1361
1374
  this.indentLevel--;
1362
1375
  lines.push(this.indent() + '}');
1363
1376
  } else {
1364
- lines.push(...stmts.map(s => this.addSemicolon(s, this.generate(s, 'statement'))));
1377
+ lines.push(...stmts.map(s => this.addSemicolon(s, this.emit(s, 'statement'))));
1365
1378
  }
1366
1379
  this.indentLevel--;
1367
1380
  return loopHeader + `{\n${lines.map(s => this.indent() + s).join('\n')}\n${this.indent()}}`;
@@ -1369,24 +1382,24 @@ export class CodeGenerator {
1369
1382
 
1370
1383
  if (noVar) {
1371
1384
  return guard
1372
- ? loopHeader + `{ if (${this.generate(guard, 'value')}) ${this.generate(body, 'statement')}; }`
1373
- : loopHeader + `{ ${this.generate(body, 'statement')}; }`;
1385
+ ? loopHeader + `{ if (${this.emit(guard, 'value')}) ${this.emit(body, 'statement')}; }`
1386
+ : loopHeader + `{ ${this.emit(body, 'statement')}; }`;
1374
1387
  }
1375
1388
  return guard
1376
- ? loopHeader + `{ const ${itemVarPattern} = ${iterCode}[${idxName}]; if (${this.generate(guard, 'value')}) ${this.generate(body, 'statement')}; }`
1377
- : loopHeader + `{ const ${itemVarPattern} = ${iterCode}[${idxName}]; ${this.generate(body, 'statement')}; }`;
1389
+ ? loopHeader + `{ const ${itemVarPattern} = ${iterCode}[${idxName}]; if (${this.emit(guard, 'value')}) ${this.emit(body, 'statement')}; }`
1390
+ : loopHeader + `{ const ${itemVarPattern} = ${iterCode}[${idxName}]; ${this.emit(body, 'statement')}; }`;
1378
1391
  }
1379
1392
 
1380
1393
  // Index variable → traditional for loop
1381
1394
  if (indexVar) {
1382
- let iterCode = this.generate(iterable, 'value');
1395
+ let iterCode = this.emit(iterable, 'value');
1383
1396
  let code = `for (let ${indexVar} = 0; ${indexVar} < ${iterCode}.length; ${indexVar}++) `;
1384
1397
  if (this.is(body, 'block')) {
1385
1398
  code += '{\n';
1386
1399
  this.indentLevel++;
1387
1400
  code += this.indent() + `const ${itemVarPattern} = ${iterCode}[${indexVar}];\n`;
1388
1401
  if (guard) {
1389
- code += this.indent() + `if (${this.unwrap(this.generate(guard, 'value'))}) {\n`;
1402
+ code += this.indent() + `if (${this.unwrap(this.emit(guard, 'value'))}) {\n`;
1390
1403
  this.indentLevel++;
1391
1404
  code += this.formatStatements(body.slice(1)).join('\n') + '\n';
1392
1405
  this.indentLevel--;
@@ -1398,8 +1411,8 @@ export class CodeGenerator {
1398
1411
  code += this.indent() + '}';
1399
1412
  } else {
1400
1413
  code += guard
1401
- ? `{ const ${itemVarPattern} = ${iterCode}[${indexVar}]; if (${this.unwrap(this.generate(guard, 'value'))}) ${this.generate(body, 'statement')}; }`
1402
- : `{ const ${itemVarPattern} = ${iterCode}[${indexVar}]; ${this.generate(body, 'statement')}; }`;
1414
+ ? `{ const ${itemVarPattern} = ${iterCode}[${indexVar}]; if (${this.unwrap(this.emit(guard, 'value'))}) ${this.emit(body, 'statement')}; }`
1415
+ : `{ const ${itemVarPattern} = ${iterCode}[${indexVar}]; ${this.emit(body, 'statement')}; }`;
1403
1416
  }
1404
1417
  return code;
1405
1418
  }
@@ -1413,41 +1426,41 @@ export class CodeGenerator {
1413
1426
  let isSimple = (e) => typeof e === 'number' || typeof e === 'string' && !e.includes('(') ||
1414
1427
  (e instanceof String && !str(e).includes('(')) || (this.is(e, '.'));
1415
1428
  if (isSimple(start) && isSimple(end)) {
1416
- let s = this.generate(start, 'value'), e = this.generate(end, 'value');
1429
+ let s = this.emit(start, 'value'), e = this.emit(end, 'value');
1417
1430
  let cmp = isExcl ? '<' : '<=';
1418
- let inc = step ? `${itemVarPattern} += ${this.generate(step, 'value')}` : `${itemVarPattern}++`;
1431
+ let inc = step ? `${itemVarPattern} += ${this.emit(step, 'value')}` : `${itemVarPattern}++`;
1419
1432
  let code = `for (let ${itemVarPattern} = ${s}; ${itemVarPattern} ${cmp} ${e}; ${inc}) `;
1420
- code += guard ? this.generateLoopBodyWithGuard(body, guard) : this.generateLoopBody(body);
1433
+ code += guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body);
1421
1434
  return code;
1422
1435
  }
1423
1436
  }
1424
1437
 
1425
1438
  // Default: for-of
1426
- let code = `for (const ${itemVarPattern} of ${this.generate(iterable, 'value')}) `;
1427
- code += guard ? this.generateLoopBodyWithGuard(body, guard) : this.generateLoopBody(body);
1439
+ let code = `for (const ${itemVarPattern} of ${this.emit(iterable, 'value')}) `;
1440
+ code += guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body);
1428
1441
  return code;
1429
1442
  }
1430
1443
 
1431
- generateForOf(head, rest, context, sexpr) {
1444
+ emitForOf(head, rest, context, sexpr) {
1432
1445
  let [vars, obj, own, guard, body] = rest;
1433
1446
 
1434
1447
  if (context === 'value' && this.comprehensionDepth === 0) {
1435
1448
  let iterator = ['for-of', vars, obj, own];
1436
- return this.generate(['comprehension', body, [iterator], guard ? [guard] : []], context);
1449
+ return this.emit(['comprehension', body, [iterator], guard ? [guard] : []], context);
1437
1450
  }
1438
1451
 
1439
1452
  let [keyVar, valueVar] = Array.isArray(vars) ? vars : [vars];
1440
- let objCode = this.generate(obj, 'value');
1453
+ let objCode = this.emit(obj, 'value');
1441
1454
  let code = `for (const ${keyVar} in ${objCode}) `;
1442
1455
 
1443
1456
  if (own && !valueVar && !guard) {
1444
1457
  if (this.is(body, 'block')) {
1445
1458
  this.indentLevel++;
1446
- let stmts = [`if (!Object.hasOwn(${objCode}, ${keyVar})) continue;`, ...body.slice(1).map(s => this.addSemicolon(s, this.generate(s, 'statement')))];
1459
+ let stmts = [`if (!Object.hasOwn(${objCode}, ${keyVar})) continue;`, ...body.slice(1).map(s => this.addSemicolon(s, this.emit(s, 'statement')))];
1447
1460
  this.indentLevel--;
1448
1461
  return code + `{\n${stmts.map(s => this.indent() + s).join('\n')}\n${this.indent()}}`;
1449
1462
  }
1450
- return code + `{ if (!Object.hasOwn(${objCode}, ${keyVar})) continue; ${this.generate(body, 'statement')}; }`;
1463
+ return code + `{ if (!Object.hasOwn(${objCode}, ${keyVar})) continue; ${this.emit(body, 'statement')}; }`;
1451
1464
  }
1452
1465
 
1453
1466
  if (valueVar) {
@@ -1458,13 +1471,13 @@ export class CodeGenerator {
1458
1471
  if (own) lines.push(`if (!Object.hasOwn(${objCode}, ${keyVar})) continue;`);
1459
1472
  lines.push(`const ${valueVar} = ${objCode}[${keyVar}];`);
1460
1473
  if (guard) {
1461
- lines.push(`if (${this.generate(guard, 'value')}) {`);
1474
+ lines.push(`if (${this.emit(guard, 'value')}) {`);
1462
1475
  this.indentLevel++;
1463
- lines.push(...stmts.map(s => this.addSemicolon(s, this.generate(s, 'statement'))));
1476
+ lines.push(...stmts.map(s => this.addSemicolon(s, this.emit(s, 'statement'))));
1464
1477
  this.indentLevel--;
1465
1478
  lines.push(this.indent() + '}');
1466
1479
  } else {
1467
- lines.push(...stmts.map(s => this.addSemicolon(s, this.generate(s, 'statement'))));
1480
+ lines.push(...stmts.map(s => this.addSemicolon(s, this.emit(s, 'statement'))));
1468
1481
  }
1469
1482
  this.indentLevel--;
1470
1483
  return code + `{\n${lines.map(s => this.indent() + s).join('\n')}\n${this.indent()}}`;
@@ -1472,16 +1485,16 @@ export class CodeGenerator {
1472
1485
  let inline = '';
1473
1486
  if (own) inline += `if (!Object.hasOwn(${objCode}, ${keyVar})) continue; `;
1474
1487
  inline += `const ${valueVar} = ${objCode}[${keyVar}]; `;
1475
- if (guard) inline += `if (${this.generate(guard, 'value')}) `;
1476
- inline += `${this.generate(body, 'statement')};`;
1488
+ if (guard) inline += `if (${this.emit(guard, 'value')}) `;
1489
+ inline += `${this.emit(body, 'statement')};`;
1477
1490
  return code + `{ ${inline} }`;
1478
1491
  }
1479
1492
 
1480
- code += guard ? this.generateLoopBodyWithGuard(body, guard) : this.generateLoopBody(body);
1493
+ code += guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body);
1481
1494
  return code;
1482
1495
  }
1483
1496
 
1484
- generateForAs(head, rest, context, sexpr) {
1497
+ emitForAs(head, rest, context, sexpr) {
1485
1498
  let varsArray = Array.isArray(rest[0]) ? rest[0] : [rest[0]];
1486
1499
  let [firstVar] = varsArray;
1487
1500
  let iterable = rest[1], isAwait = rest[2], guard = rest[3], body = rest[4];
@@ -1496,9 +1509,9 @@ export class CodeGenerator {
1496
1509
  let beforeRest = elements.slice(0, restIdx);
1497
1510
  let restEl = elements[restIdx];
1498
1511
  let restVar = this.is(restEl, '...') ? restEl[1] : '_rest';
1499
- let beforePattern = beforeRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.generate(el, 'value')).join(', ');
1512
+ let beforePattern = beforeRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.emit(el, 'value')).join(', ');
1500
1513
  let firstPattern = beforePattern ? `${beforePattern}, ...${restVar}` : `...${restVar}`;
1501
- let afterPattern = afterRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.generate(el, 'value')).join(', ');
1514
+ let afterPattern = afterRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.emit(el, 'value')).join(', ');
1502
1515
  destructStmts.push(`[${firstPattern}] = _item`);
1503
1516
  destructStmts.push(`[${afterPattern}] = ${restVar}.splice(-${afterCount})`);
1504
1517
  this.helpers.add('slice');
@@ -1510,12 +1523,12 @@ export class CodeGenerator {
1510
1523
  }
1511
1524
  }
1512
1525
 
1513
- let iterCode = this.generate(iterable, 'value');
1526
+ let iterCode = this.emit(iterable, 'value');
1514
1527
  let awaitKw = isAwait ? 'await ' : '';
1515
1528
  let itemVarPattern;
1516
1529
  if (needsTempVar) itemVarPattern = '_item';
1517
1530
  else if ((this.is(firstVar, 'array') || this.is(firstVar, 'object')))
1518
- itemVarPattern = this.generateDestructuringPattern(firstVar);
1531
+ itemVarPattern = this.emitDestructuringPattern(firstVar);
1519
1532
  else itemVarPattern = firstVar;
1520
1533
 
1521
1534
  let code = `for ${awaitKw}(const ${itemVarPattern} of ${iterCode}) `;
@@ -1528,26 +1541,26 @@ export class CodeGenerator {
1528
1541
  ]);
1529
1542
  code += `{\n${allStmts.join('\n')}\n${this.indent()}}`;
1530
1543
  } else {
1531
- code += guard ? this.generateLoopBodyWithGuard(body, guard) : this.generateLoopBody(body);
1544
+ code += guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body);
1532
1545
  }
1533
1546
  return code;
1534
1547
  }
1535
1548
 
1536
- generateWhile(head, rest) {
1549
+ emitWhile(head, rest) {
1537
1550
  let cond = rest[0], guard = rest.length === 3 ? rest[1] : null, body = rest[rest.length - 1];
1538
- let code = `while (${this.unwrap(this.generate(cond, 'value'))}) `;
1539
- return code + (guard ? this.generateLoopBodyWithGuard(body, guard) : this.generateLoopBody(body));
1551
+ let code = `while (${this.unwrap(this.emit(cond, 'value'))}) `;
1552
+ return code + (guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body));
1540
1553
  }
1541
1554
 
1542
- generateRange(head, rest) {
1555
+ emitRange(head, rest) {
1543
1556
  if (head === '...') {
1544
- if (rest.length === 1) return `...${this.generate(rest[0], 'value')}`;
1557
+ if (rest.length === 1) return `...${this.emit(rest[0], 'value')}`;
1545
1558
  let [s, e] = rest;
1546
- let sc = this.generate(s, 'value'), ec = this.generate(e, 'value');
1559
+ let sc = this.emit(s, 'value'), ec = this.emit(e, 'value');
1547
1560
  return `((s, e) => Array.from({length: Math.max(0, Math.abs(e - s))}, (_, i) => s + (i * (s <= e ? 1 : -1))))(${sc}, ${ec})`;
1548
1561
  }
1549
1562
  let [s, e] = rest;
1550
- let sc = this.generate(s, 'value'), ec = this.generate(e, 'value');
1563
+ let sc = this.emit(s, 'value'), ec = this.emit(e, 'value');
1551
1564
  return `((s, e) => Array.from({length: Math.abs(e - s) + 1}, (_, i) => s + (i * (s <= e ? 1 : -1))))(${sc}, ${ec})`;
1552
1565
  }
1553
1566
 
@@ -1555,134 +1568,134 @@ export class CodeGenerator {
1555
1568
  // Unary operators
1556
1569
  // ---------------------------------------------------------------------------
1557
1570
 
1558
- generateNot(head, rest) {
1571
+ emitNot(head, rest) {
1559
1572
  let [operand] = rest;
1560
- if (typeof operand === 'string' || operand instanceof String) return `!${this.generate(operand, 'value')}`;
1573
+ if (typeof operand === 'string' || operand instanceof String) return `!${this.emit(operand, 'value')}`;
1561
1574
  if (Array.isArray(operand)) {
1562
1575
  let highPrec = ['.', '?.', '[]', 'optindex', 'optcall'];
1563
- if (highPrec.includes(operand[0])) return `!${this.generate(operand, 'value')}`;
1576
+ if (highPrec.includes(operand[0])) return `!${this.emit(operand, 'value')}`;
1564
1577
  }
1565
- let code = this.generate(operand, 'value');
1578
+ let code = this.emit(operand, 'value');
1566
1579
  return code.startsWith('(') ? `!${code}` : `(!${code})`;
1567
1580
  }
1568
1581
 
1569
- generateBitwiseNot(head, rest) { return `(~${this.generate(rest[0], 'value')})`; }
1582
+ emitBitwiseNot(head, rest) { return `(~${this.emit(rest[0], 'value')})`; }
1570
1583
 
1571
- generateIncDec(head, rest) {
1584
+ emitIncDec(head, rest) {
1572
1585
  let [operand, isPostfix] = rest;
1573
- let code = this.generate(operand, 'value');
1586
+ let code = this.emit(operand, 'value');
1574
1587
  return isPostfix ? `(${code}${head})` : `(${head}${code})`;
1575
1588
  }
1576
1589
 
1577
- generateTypeof(head, rest) { return `typeof ${this.generate(rest[0], 'value')}`; }
1578
- generateDelete(head, rest) { return `(delete ${this.generate(rest[0], 'value')})`; }
1590
+ emitTypeof(head, rest) { return `typeof ${this.emit(rest[0], 'value')}`; }
1591
+ emitDelete(head, rest) { return `(delete ${this.emit(rest[0], 'value')})`; }
1579
1592
 
1580
- generateInstanceof(head, rest, context, sexpr) {
1593
+ emitInstanceof(head, rest, context, sexpr) {
1581
1594
  let [expr, type] = rest;
1582
1595
  let isNeg = meta(sexpr[0], 'invert');
1583
- let result = `(${this.generate(expr, 'value')} instanceof ${this.generate(type, 'value')})`;
1596
+ let result = `(${this.emit(expr, 'value')} instanceof ${this.emit(type, 'value')})`;
1584
1597
  return isNeg ? `(!${result})` : result;
1585
1598
  }
1586
1599
 
1587
- generateIn(head, rest, context, sexpr) {
1600
+ emitIn(head, rest, context, sexpr) {
1588
1601
  let [key, container] = rest;
1589
- let keyCode = this.generate(key, 'value');
1602
+ let keyCode = this.emit(key, 'value');
1590
1603
  let isNeg = meta(sexpr[0], 'invert');
1591
1604
  if (this.is(container, 'object')) {
1592
- let result = `(${keyCode} in ${this.generate(container, 'value')})`;
1605
+ let result = `(${keyCode} in ${this.emit(container, 'value')})`;
1593
1606
  return isNeg ? `(!${result})` : result;
1594
1607
  }
1595
- let c = this.generate(container, 'value');
1608
+ let c = this.emit(container, 'value');
1596
1609
  let result = `(Array.isArray(${c}) || typeof ${c} === 'string' ? ${c}.includes(${keyCode}) : (${keyCode} in ${c}))`;
1597
1610
  return isNeg ? `(!${result})` : result;
1598
1611
  }
1599
1612
 
1600
- generateOf(head, rest, context, sexpr) {
1613
+ emitOf(head, rest, context, sexpr) {
1601
1614
  let [value, container] = rest;
1602
- let v = this.generate(value, 'value'), c = this.generate(container, 'value');
1615
+ let v = this.emit(value, 'value'), c = this.emit(container, 'value');
1603
1616
  let isNeg = meta(sexpr[0], 'invert');
1604
1617
  let result = `(${v} in ${c})`;
1605
1618
  return isNeg ? `(!${result})` : result;
1606
1619
  }
1607
1620
 
1608
- generateRegexMatch(head, rest) {
1621
+ emitRegexMatch(head, rest) {
1609
1622
  let [left, right] = rest;
1610
1623
  this.helpers.add('toMatchable');
1611
1624
  this.programVars.add('_');
1612
- let r = this.generate(right, 'value');
1625
+ let r = this.emit(right, 'value');
1613
1626
  let allowNL = r.includes('/m') ? ', true' : '';
1614
- return `(_ = toMatchable(${this.generate(left, 'value')}${allowNL}).match(${r}))`;
1627
+ return `(_ = toMatchable(${this.emit(left, 'value')}${allowNL}).match(${r}))`;
1615
1628
  }
1616
1629
 
1617
- generateNew(head, rest) {
1630
+ emitNew(head, rest) {
1618
1631
  let [call] = rest;
1619
1632
  if ((this.is(call, '.') || this.is(call, '?.'))) {
1620
1633
  let [accType, target, prop] = call;
1621
1634
  if (Array.isArray(target) && !target[0].startsWith) {
1622
- return `(${this.generate(['new', target], 'value')}).${prop}`;
1635
+ return `(${this.emit(['new', target], 'value')}).${prop}`;
1623
1636
  }
1624
- return `new ${this.generate(target, 'value')}.${prop}`;
1637
+ return `new ${this.emit(target, 'value')}.${prop}`;
1625
1638
  }
1626
1639
  if (Array.isArray(call)) {
1627
1640
  let [ctor, ...args] = call;
1628
- return `new ${this.generate(ctor, 'value')}(${args.map(a => this.unwrap(this.generate(a, 'value'))).join(', ')})`;
1641
+ return `new ${this.emit(ctor, 'value')}(${args.map(a => this.unwrap(this.emit(a, 'value'))).join(', ')})`;
1629
1642
  }
1630
- return `new ${this.generate(call, 'value')}()`;
1643
+ return `new ${this.emit(call, 'value')}()`;
1631
1644
  }
1632
1645
 
1633
1646
  // ---------------------------------------------------------------------------
1634
1647
  // Logical operators
1635
1648
  // ---------------------------------------------------------------------------
1636
1649
 
1637
- generateLogicalAnd(head, rest, context, sexpr) {
1650
+ emitLogicalAnd(head, rest, context, sexpr) {
1638
1651
  let ops = this.flattenBinaryChain(sexpr).slice(1);
1639
1652
  if (ops.length === 0) return 'true';
1640
- if (ops.length === 1) return this.generate(ops[0], 'value');
1641
- return `(${ops.map(o => this.generate(o, 'value')).join(' && ')})`;
1653
+ if (ops.length === 1) return this.emit(ops[0], 'value');
1654
+ return `(${ops.map(o => this.emit(o, 'value')).join(' && ')})`;
1642
1655
  }
1643
1656
 
1644
- generateLogicalOr(head, rest, context, sexpr) {
1657
+ emitLogicalOr(head, rest, context, sexpr) {
1645
1658
  let ops = this.flattenBinaryChain(sexpr).slice(1);
1646
1659
  if (ops.length === 0) return 'true';
1647
- if (ops.length === 1) return this.generate(ops[0], 'value');
1648
- return `(${ops.map(o => this.generate(o, 'value')).join(' || ')})`;
1660
+ if (ops.length === 1) return this.emit(ops[0], 'value');
1661
+ return `(${ops.map(o => this.emit(o, 'value')).join(' || ')})`;
1649
1662
  }
1650
1663
 
1651
1664
  // ---------------------------------------------------------------------------
1652
1665
  // Data structures
1653
1666
  // ---------------------------------------------------------------------------
1654
1667
 
1655
- generateArray(head, elements) {
1668
+ emitArray(head, elements) {
1656
1669
  let hasTrailingElision = elements.length > 0 && elements[elements.length - 1] === ',';
1657
1670
  let codes = elements.map(el => {
1658
1671
  if (el === ',') return '';
1659
1672
  if (el === '...') return '';
1660
- if (this.is(el, '...')) return `...${this.generate(el[1], 'value')}`;
1661
- return this.generate(el, 'value');
1673
+ if (this.is(el, '...')) return `...${this.emit(el[1], 'value')}`;
1674
+ return this.emit(el, 'value');
1662
1675
  }).join(', ');
1663
1676
  return hasTrailingElision ? `[${codes},]` : `[${codes}]`;
1664
1677
  }
1665
1678
 
1666
- generateObject(head, pairs, context) {
1679
+ emitObject(head, pairs, context) {
1667
1680
  if (pairs.length === 1 && Array.isArray(pairs[0]) &&
1668
1681
  Array.isArray(pairs[0][2]) && pairs[0][2][0] === 'comprehension') {
1669
1682
  let [, keyVar, compNode] = pairs[0];
1670
1683
  let [, valueExpr, iterators, guards] = compNode;
1671
- return this.generate(['object-comprehension', keyVar, valueExpr, iterators, guards], context);
1684
+ return this.emit(['object-comprehension', keyVar, valueExpr, iterators, guards], context);
1672
1685
  }
1673
1686
 
1674
1687
  let codes = pairs.map(pair => {
1675
- if (this.is(pair, '...')) return `...${this.generate(pair[1], 'value')}`;
1688
+ if (this.is(pair, '...')) return `...${this.emit(pair[1], 'value')}`;
1676
1689
  let [operator, key, value] = pair;
1677
1690
  let keyCode;
1678
- if (this.is(key, 'dynamicKey')) keyCode = `[${this.generate(key[1], 'value')}]`;
1679
- else if (this.is(key, 'str')) keyCode = `[${this.generate(key, 'value')}]`;
1691
+ if (this.is(key, 'dynamicKey')) keyCode = `[${this.emit(key[1], 'value')}]`;
1692
+ else if (this.is(key, 'str')) keyCode = `[${this.emit(key, 'value')}]`;
1680
1693
  else {
1681
1694
  this.suppressReactiveUnwrap = true;
1682
- keyCode = this.generate(key, 'value');
1695
+ keyCode = this.emit(key, 'value');
1683
1696
  this.suppressReactiveUnwrap = false;
1684
1697
  }
1685
- let valCode = this.generate(value, 'value');
1698
+ let valCode = this.emit(value, 'value');
1686
1699
  if (operator === '=') return `${keyCode} = ${valCode}`;
1687
1700
  if (operator === ':') return `${keyCode}: ${valCode}`;
1688
1701
  if (keyCode === valCode && !Array.isArray(key)) return keyCode;
@@ -1691,59 +1704,59 @@ export class CodeGenerator {
1691
1704
  return `{${codes}}`;
1692
1705
  }
1693
1706
 
1694
- generateMap(head, pairs, context) {
1707
+ emitMap(head, pairs, context) {
1695
1708
  if (pairs.length === 0) return 'new Map()';
1696
1709
  let entries = pairs.map(pair => {
1697
- if (this.is(pair, '...')) return `...${this.generate(pair[1], 'value')}`;
1710
+ if (this.is(pair, '...')) return `...${this.emit(pair[1], 'value')}`;
1698
1711
  let [, key, value] = pair;
1699
1712
  let keyCode;
1700
1713
  if (Array.isArray(key)) {
1701
- keyCode = this.generate(key, 'value');
1714
+ keyCode = this.emit(key, 'value');
1702
1715
  } else {
1703
1716
  let k = str(key) ?? key;
1704
1717
  let isIdentifier = !k.startsWith('"') && !k.startsWith("'") && !k.startsWith('/') &&
1705
- !CodeGenerator.NUMBER_START_RE.test(k) && !MAP_LITERAL_KEYS.has(k);
1706
- keyCode = isIdentifier ? `"${k}"` : this.generate(key, 'value');
1718
+ !CodeEmitter.NUMBER_START_RE.test(k) && !MAP_LITERAL_KEYS.has(k);
1719
+ keyCode = isIdentifier ? `"${k}"` : this.emit(key, 'value');
1707
1720
  }
1708
- let valCode = this.generate(value, 'value');
1721
+ let valCode = this.emit(value, 'value');
1709
1722
  return `[${keyCode}, ${valCode}]`;
1710
1723
  }).join(', ');
1711
1724
  return `new Map([${entries}])`;
1712
1725
  }
1713
1726
 
1714
- generateBlock(head, statements, context) {
1727
+ emitBlock(head, statements, context) {
1715
1728
  if (context === 'statement') {
1716
1729
  let stmts = this.withIndent(() => this.formatStatements(statements));
1717
1730
  return `{\n${stmts.join('\n')}\n${this.indent()}}`;
1718
1731
  }
1719
1732
  if (statements.length === 0) return 'undefined';
1720
- if (statements.length === 1) return this.generate(statements[0], context);
1733
+ if (statements.length === 1) return this.emit(statements[0], context);
1721
1734
  let last = statements[statements.length - 1];
1722
1735
  let lastIsCtrl = Array.isArray(last) && ['break', 'continue', 'return', 'throw'].includes(last[0]);
1723
1736
  if (lastIsCtrl) {
1724
- let parts = statements.map(s => this.addSemicolon(s, this.generate(s, 'statement')));
1737
+ let parts = statements.map(s => this.addSemicolon(s, this.emit(s, 'statement')));
1725
1738
  return `{\n${this.withIndent(() => parts.map(p => this.indent() + p).join('\n'))}\n${this.indent()}}`;
1726
1739
  }
1727
- return `(${statements.map(s => this.generate(s, 'value')).join(', ')})`;
1740
+ return `(${statements.map(s => this.emit(s, 'value')).join(', ')})`;
1728
1741
  }
1729
1742
 
1730
1743
  // ---------------------------------------------------------------------------
1731
1744
  // Exception handling
1732
1745
  // ---------------------------------------------------------------------------
1733
1746
 
1734
- generateTry(head, rest, context) {
1747
+ emitTry(head, rest, context) {
1735
1748
  let needsReturns = context === 'value';
1736
1749
  let tryCode = 'try ';
1737
1750
  let tryBlock = rest[0];
1738
1751
  tryCode += (needsReturns && this.is(tryBlock, 'block'))
1739
- ? this.generateBlockWithReturns(tryBlock) : this.generate(tryBlock, 'statement');
1752
+ ? this.emitBlockWithReturns(tryBlock) : this.emit(tryBlock, 'statement');
1740
1753
 
1741
1754
  if (rest.length >= 2 && Array.isArray(rest[1]) && rest[1].length === 2 && rest[1][0] !== 'block') {
1742
1755
  let [param, catchBlock] = rest[1];
1743
1756
  tryCode += ' catch';
1744
1757
  if (param && (this.is(param, 'object') || this.is(param, 'array'))) {
1745
1758
  tryCode += ' (error)';
1746
- let destructStmt = `(${this.generate(param, 'value')} = error)`;
1759
+ let destructStmt = `(${this.emit(param, 'value')} = error)`;
1747
1760
  catchBlock = this.is(catchBlock, 'block')
1748
1761
  ? ['block', destructStmt, ...catchBlock.slice(1)]
1749
1762
  : ['block', destructStmt, catchBlock];
@@ -1751,23 +1764,24 @@ export class CodeGenerator {
1751
1764
  tryCode += ` (${param})`;
1752
1765
  }
1753
1766
  tryCode += ' ' + ((needsReturns && this.is(catchBlock, 'block'))
1754
- ? this.generateBlockWithReturns(catchBlock) : this.generate(catchBlock, 'statement'));
1767
+ ? this.emitBlockWithReturns(catchBlock) : this.emit(catchBlock, 'statement'));
1755
1768
  } else if (rest.length === 2) {
1756
- tryCode += ' finally ' + this.generate(rest[1], 'statement');
1769
+ tryCode += ' finally ' + this.emit(rest[1], 'statement');
1757
1770
  }
1758
1771
 
1759
- if (rest.length === 3) tryCode += ' finally ' + this.generate(rest[2], 'statement');
1772
+ if (rest.length === 3) tryCode += ' finally ' + this.emit(rest[2], 'statement');
1760
1773
 
1761
1774
  if (rest.length === 1) tryCode += ' catch {}';
1762
1775
 
1763
1776
  if (needsReturns) {
1764
- let isAsync = this.containsAwait(rest[0]) || (rest[1] && this.containsAwait(rest[1]));
1765
- return `(${isAsync ? 'async ' : ''}() => { ${tryCode} })()`;
1777
+ // Enclosed: rest[0] (try body), rest[1] (catch clause), rest[2] (finally block)
1778
+ let hasAwait = this.containsAwait(rest[0]) || (rest[1] && this.containsAwait(rest[1])) || (rest[2] && this.containsAwait(rest[2]));
1779
+ return this.asyncIIFE(hasAwait, tryCode);
1766
1780
  }
1767
1781
  return tryCode;
1768
1782
  }
1769
1783
 
1770
- generateThrow(head, rest, context) {
1784
+ emitThrow(head, rest, context) {
1771
1785
  let [expr] = rest;
1772
1786
  if (Array.isArray(expr)) {
1773
1787
  let checkExpr = expr, wrapperType = null;
@@ -1780,24 +1794,24 @@ export class CodeGenerator {
1780
1794
  let [, condition, body] = checkExpr;
1781
1795
  let unwrapped = Array.isArray(body) && body.length === 1 ? body[0] : body;
1782
1796
  expr = wrapperType === 'new' ? ['new', unwrapped] : unwrapped;
1783
- let condCode = this.generate(condition, 'value');
1784
- let throwCode = `throw ${this.generate(expr, 'value')}`;
1797
+ let condCode = this.emit(condition, 'value');
1798
+ let throwCode = `throw ${this.emit(expr, 'value')}`;
1785
1799
  return `if (${condCode}) {\n${this.indent()} ${throwCode};\n${this.indent()}}`;
1786
1800
  }
1787
1801
  }
1788
- let throwStmt = `throw ${this.generate(expr, 'value')}`;
1802
+ let throwStmt = `throw ${this.emit(expr, 'value')}`;
1789
1803
  return context === 'value' ? `(() => { ${throwStmt}; })()` : throwStmt;
1790
1804
  }
1791
1805
 
1792
- generateControl(head, rest, context) {
1806
+ emitControl(head, rest, context) {
1793
1807
  let [rawOp, expr, ctrlSexpr] = rest;
1794
1808
  let op = str(rawOp);
1795
1809
  let isReturn = ctrlSexpr[0] === 'return';
1796
- let exprCode = this.generate(expr, 'value');
1810
+ let exprCode = this.emit(expr, 'value');
1797
1811
  let ctrlValue = ctrlSexpr.length > 1 ? ctrlSexpr[1] : null;
1798
1812
  let ctrlCode = isReturn
1799
- ? (ctrlValue ? `return ${this.generate(ctrlValue, 'value')}` : 'return')
1800
- : (ctrlValue ? `throw ${this.generate(ctrlValue, 'value')}` : 'throw new Error()');
1813
+ ? (ctrlValue ? `return ${this.emit(ctrlValue, 'value')}` : 'return')
1814
+ : (ctrlValue ? `throw ${this.emit(ctrlValue, 'value')}` : 'throw new Error()');
1801
1815
  let wrapped = this.wrapForCondition(exprCode);
1802
1816
 
1803
1817
  if (context === 'value') {
@@ -1814,43 +1828,44 @@ export class CodeGenerator {
1814
1828
  // Switch
1815
1829
  // ---------------------------------------------------------------------------
1816
1830
 
1817
- generateSwitch(head, rest, context) {
1831
+ emitSwitch(head, rest, context) {
1818
1832
  let [disc, whens, defaultCase] = rest;
1819
- if (disc === null) return this.generateSwitchAsIfChain(whens, defaultCase, context);
1833
+ if (disc === null) return this.emitSwitchAsIfChain(whens, defaultCase, context);
1820
1834
 
1821
- let switchBody = `switch (${this.generate(disc, 'value')}) {\n`;
1835
+ let switchBody = `switch (${this.emit(disc, 'value')}) {\n`;
1822
1836
  this.indentLevel++;
1823
1837
  for (let clause of whens) {
1824
1838
  let [, test, body] = clause;
1825
1839
  for (let t of test) {
1826
1840
  let tv = str(t) ?? t;
1827
1841
  let cv;
1828
- if (Array.isArray(tv)) cv = this.generate(tv, 'value');
1842
+ if (Array.isArray(tv)) cv = this.emit(tv, 'value');
1829
1843
  else if (typeof tv === 'string' && (tv.startsWith('"') || tv.startsWith("'"))) cv = `'${tv.slice(1, -1)}'`;
1830
- else cv = this.generate(tv, 'value');
1844
+ else cv = this.emit(tv, 'value');
1831
1845
  switchBody += this.indent() + `case ${cv}:\n`;
1832
1846
  }
1833
1847
  this.indentLevel++;
1834
- switchBody += this.generateSwitchCaseBody(body, context);
1848
+ switchBody += this.emitSwitchCaseBody(body, context);
1835
1849
  this.indentLevel--;
1836
1850
  }
1837
1851
  if (defaultCase) {
1838
1852
  switchBody += this.indent() + 'default:\n';
1839
1853
  this.indentLevel++;
1840
- switchBody += this.generateSwitchCaseBody(defaultCase, context);
1854
+ switchBody += this.emitSwitchCaseBody(defaultCase, context);
1841
1855
  this.indentLevel--;
1842
1856
  }
1843
1857
  this.indentLevel--;
1844
1858
  switchBody += this.indent() + '}';
1845
1859
 
1846
1860
  if (context === 'value') {
1847
- let hasAwait = whens.some(w => this.containsAwait(w[2])) || (defaultCase && this.containsAwait(defaultCase));
1848
- return `(${hasAwait ? 'async ' : ''}() => { ${switchBody} })()`;
1861
+ // Enclosed: disc (discriminant), w[1] (case labels), w[2] (case bodies), defaultCase
1862
+ let hasAwait = this.containsAwait(disc) || whens.some(w => this.containsAwait(w[1]) || this.containsAwait(w[2])) || (defaultCase && this.containsAwait(defaultCase));
1863
+ return this.asyncIIFE(hasAwait, switchBody);
1849
1864
  }
1850
1865
  return switchBody;
1851
1866
  }
1852
1867
 
1853
- generateWhen() { throw new Error('when clause should be handled by switch'); }
1868
+ emitWhen(head, rest, context, sexpr) { this.error('when clause should be handled by switch', sexpr); }
1854
1869
 
1855
1870
  // ---------------------------------------------------------------------------
1856
1871
  // Comprehensions
@@ -1864,7 +1879,7 @@ export class CodeGenerator {
1864
1879
  let noVar = va.length === 0;
1865
1880
  let [itemVar, indexVar] = noVar ? ['_i', null] : va;
1866
1881
  let ivp = (this.is(itemVar, 'array') || this.is(itemVar, 'object'))
1867
- ? this.generateDestructuringPattern(itemVar) : itemVar;
1882
+ ? this.emitDestructuringPattern(itemVar) : itemVar;
1868
1883
 
1869
1884
  if (step && step !== null) {
1870
1885
  let ih = Array.isArray(iterable) && iterable[0];
@@ -1873,10 +1888,10 @@ export class CodeGenerator {
1873
1888
  if (isRange) {
1874
1889
  let isExcl = ih === '...';
1875
1890
  let [s, e] = iterable.slice(1);
1876
- let sc = this.generate(s, 'value'), ec = this.generate(e, 'value'), stc = this.generate(step, 'value');
1891
+ let sc = this.emit(s, 'value'), ec = this.emit(e, 'value'), stc = this.emit(step, 'value');
1877
1892
  return { header: `for (let ${ivp} = ${sc}; ${ivp} ${isExcl ? '<' : '<='} ${ec}; ${ivp} += ${stc})`, setup: null };
1878
1893
  }
1879
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
1894
+ let ic = this.emit(iterable, 'value'), idxN = indexVar || '_i', stc = this.emit(step, 'value');
1880
1895
  let isNeg = this.is(step, '-', 1);
1881
1896
  let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
1882
1897
  let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
@@ -1887,13 +1902,13 @@ export class CodeGenerator {
1887
1902
  return { header, setup: noVar ? null : `const ${ivp} = ${ic}[${idxN}];` };
1888
1903
  }
1889
1904
  if (indexVar) {
1890
- let ic = this.generate(iterable, 'value');
1905
+ let ic = this.emit(iterable, 'value');
1891
1906
  return {
1892
1907
  header: `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++)`,
1893
1908
  setup: `const ${ivp} = ${ic}[${indexVar}];`,
1894
1909
  };
1895
1910
  }
1896
- return { header: `for (const ${ivp} of ${this.generate(iterable, 'value')})`, setup: null };
1911
+ return { header: `for (const ${ivp} of ${this.emit(iterable, 'value')})`, setup: null };
1897
1912
  }
1898
1913
 
1899
1914
  // Shared: parse a for-of (object) iterator and return { header, own, vv, oc, kvp }.
@@ -1901,8 +1916,8 @@ export class CodeGenerator {
1901
1916
  let va = Array.isArray(vars) ? vars : [vars];
1902
1917
  let [kv, vv] = va;
1903
1918
  let kvp = (this.is(kv, 'array') || this.is(kv, 'object'))
1904
- ? this.generateDestructuringPattern(kv) : kv;
1905
- let oc = this.generate(iterable, 'value');
1919
+ ? this.emitDestructuringPattern(kv) : kv;
1920
+ let oc = this.emit(iterable, 'value');
1906
1921
  return { header: `for (const ${kvp} in ${oc})`, own, vv, oc, kvp };
1907
1922
  }
1908
1923
 
@@ -1911,17 +1926,18 @@ export class CodeGenerator {
1911
1926
  let va = Array.isArray(vars) ? vars : [vars];
1912
1927
  let [fv] = va;
1913
1928
  let ivp = (this.is(fv, 'array') || this.is(fv, 'object'))
1914
- ? this.generateDestructuringPattern(fv) : fv;
1915
- return { header: `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.generate(iterable, 'value')})` };
1929
+ ? this.emitDestructuringPattern(fv) : fv;
1930
+ return { header: `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.emit(iterable, 'value')})` };
1916
1931
  }
1917
1932
 
1918
- generateComprehension(head, rest, context) {
1933
+ emitComprehension(head, rest, context) {
1919
1934
  let [expr, iterators, guards] = rest;
1920
- if (context === 'statement') return this.generateComprehensionAsLoop(expr, iterators, guards);
1921
- if (this.comprehensionTarget) return this.generateComprehensionWithTarget(expr, iterators, guards, this.comprehensionTarget);
1935
+ if (context === 'statement') return this.emitComprehensionAsLoop(expr, iterators, guards);
1936
+ if (this.comprehensionTarget) return this.emitComprehensionWithTarget(expr, iterators, guards, this.comprehensionTarget);
1922
1937
 
1923
- let hasAwait = this.containsAwait(expr);
1924
- let code = `(${hasAwait ? 'async ' : ''}() => {\n`;
1938
+ // Enclosed: expr, iterators (iterable expressions), guards
1939
+ let hasAwait = this.containsAwait(expr) || iterators.some(i => this.containsAwait(i)) || guards.some(g => this.containsAwait(g));
1940
+ let code = this.asyncIIFEOpen(hasAwait) + '\n';
1925
1941
  this.indentLevel++;
1926
1942
  this.comprehensionDepth++;
1927
1943
  code += this.indent() + 'const result = [];\n';
@@ -1947,7 +1963,7 @@ export class CodeGenerator {
1947
1963
  }
1948
1964
 
1949
1965
  for (let guard of guards) {
1950
- code += this.indent() + `if (${this.generate(guard, 'value')}) {\n`;
1966
+ code += this.indent() + `if (${this.emit(guard, 'value')}) {\n`;
1951
1967
  this.indentLevel++;
1952
1968
  }
1953
1969
 
@@ -1964,20 +1980,20 @@ export class CodeGenerator {
1964
1980
  for (let i = 0; i < expr.length - 1; i++) {
1965
1981
  let s = expr[i + 1], isLast = i === expr.length - 2;
1966
1982
  if (!isLast || hasCtrl(s)) {
1967
- code += this.indent() + this.generate(s, 'statement') + ';\n';
1983
+ code += this.indent() + this.emit(s, 'statement') + ';\n';
1968
1984
  } else if (Array.isArray(s) && loopStmts.includes(s[0])) {
1969
- code += this.indent() + this.generate(s, 'statement') + ';\n';
1985
+ code += this.indent() + this.emit(s, 'statement') + ';\n';
1970
1986
  } else {
1971
- code += this.indent() + `result.push(${this.generate(s, 'value')});\n`;
1987
+ code += this.indent() + `result.push(${this.emit(s, 'value')});\n`;
1972
1988
  }
1973
1989
  }
1974
1990
  } else {
1975
1991
  if (hasCtrl(expr)) {
1976
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
1992
+ code += this.indent() + this.emit(expr, 'statement') + ';\n';
1977
1993
  } else if (Array.isArray(expr) && loopStmts.includes(expr[0])) {
1978
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
1994
+ code += this.indent() + this.emit(expr, 'statement') + ';\n';
1979
1995
  } else {
1980
- code += this.indent() + `result.push(${this.generate(expr, 'value')});\n`;
1996
+ code += this.indent() + `result.push(${this.emit(expr, 'value')});\n`;
1981
1997
  }
1982
1998
  }
1983
1999
 
@@ -1990,24 +2006,26 @@ export class CodeGenerator {
1990
2006
  return code;
1991
2007
  }
1992
2008
 
1993
- generateObjectComprehension(head, rest, context) {
2009
+ emitObjectComprehension(head, rest, context) {
1994
2010
  let [keyExpr, valueExpr, iterators, guards] = rest;
1995
- let code = '(() => {\n';
2011
+ // Enclosed: keyExpr, valueExpr, iterators (iterable expressions), guards
2012
+ let hasAwait = this.containsAwait(keyExpr) || this.containsAwait(valueExpr) || iterators.some(i => this.containsAwait(i)) || guards.some(g => this.containsAwait(g));
2013
+ let code = this.asyncIIFEOpen(hasAwait) + '\n';
1996
2014
  this.indentLevel++;
1997
2015
  code += this.indent() + 'const result = {};\n';
1998
2016
  for (let iter of iterators) {
1999
2017
  let [iterType, vars, iterable, own] = iter;
2000
2018
  if (iterType === 'for-of') {
2001
2019
  let [kv, vv] = vars;
2002
- let oc = this.generate(iterable, 'value');
2020
+ let oc = this.emit(iterable, 'value');
2003
2021
  code += this.indent() + `for (const ${kv} in ${oc}) {\n`;
2004
2022
  this.indentLevel++;
2005
2023
  if (own) code += this.indent() + `if (!Object.hasOwn(${oc}, ${kv})) continue;\n`;
2006
2024
  if (vv) code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2007
2025
  }
2008
2026
  }
2009
- for (let guard of guards) { code += this.indent() + `if (${this.generate(guard, 'value')}) {\n`; this.indentLevel++; }
2010
- code += this.indent() + `result[${this.generate(keyExpr, 'value')}] = ${this.generate(valueExpr, 'value')};\n`;
2027
+ for (let guard of guards) { code += this.indent() + `if (${this.emit(guard, 'value')}) {\n`; this.indentLevel++; }
2028
+ code += this.indent() + `result[${this.emit(keyExpr, 'value')}] = ${this.emit(valueExpr, 'value')};\n`;
2011
2029
  for (let i = 0; i < guards.length; i++) { this.indentLevel--; code += this.indent() + '}\n'; }
2012
2030
  for (let i = 0; i < iterators.length; i++) { this.indentLevel--; code += this.indent() + '}\n'; }
2013
2031
  code += this.indent() + 'return result;\n';
@@ -2020,10 +2038,10 @@ export class CodeGenerator {
2020
2038
  // Classes
2021
2039
  // ---------------------------------------------------------------------------
2022
2040
 
2023
- generateClass(head, rest, context) {
2041
+ emitClass(head, rest, context) {
2024
2042
  let [className, parentClass, ...bodyParts] = rest;
2025
2043
  let code = className ? `class ${className}` : 'class';
2026
- if (parentClass) code += ` extends ${this.generate(parentClass, 'value')}`;
2044
+ if (parentClass) code += ` extends ${this.emit(parentClass, 'value')}`;
2027
2045
  code += ' {\n';
2028
2046
 
2029
2047
  if (bodyParts.length > 0 && Array.isArray(bodyParts[0])) {
@@ -2032,97 +2050,20 @@ export class CodeGenerator {
2032
2050
  let bodyStmts = bodyBlock.slice(1);
2033
2051
  let hasObjFirst = bodyStmts.length > 0 && Array.isArray(bodyStmts[0]) && bodyStmts[0][0] === 'object';
2034
2052
 
2035
- if (hasObjFirst && bodyStmts.length === 1) {
2036
- let members = bodyStmts[0].slice(1);
2037
- this.indentLevel++;
2038
-
2039
- // First pass: identify bound methods
2040
- let boundMethods = [];
2041
- for (let [, mk, mv] of members) {
2042
- let isStatic = this.is(mk, '.') && mk[1] === 'this';
2043
- let isComputed = this.is(mk, 'computed');
2044
- let mName = this.extractMemberName(mk);
2045
- if (this.is(mv, '=>') && !isStatic && !isComputed && mName !== 'constructor') boundMethods.push(mName);
2046
- }
2047
-
2048
- // Second pass: generate members
2049
- for (let [, mk, mv] of members) {
2050
- let isStatic = this.is(mk, '.') && mk[1] === 'this';
2051
- let isComputed = this.is(mk, 'computed');
2052
- let mName = this.extractMemberName(mk);
2053
- if ((this.is(mv, '->') || this.is(mv, '=>'))) {
2054
- let [, params, body] = mv;
2055
- let hasAwait = this.containsAwait(body), hasYield = this.containsYield(body);
2056
- let cleanParams = params, autoAssign = [];
2057
- if (mName === 'constructor') {
2058
- let isSubclass = !!parentClass;
2059
- let atParamMap = isSubclass ? new Map() : null;
2060
- cleanParams = params.map(p => {
2061
- // Handle @param: ['.', 'this', 'name']
2062
- if (this.is(p, '.') && p[1] === 'this') {
2063
- let name = p[2];
2064
- let param = isSubclass ? `_${name}` : name;
2065
- autoAssign.push(`this.${name} = ${param}`);
2066
- if (isSubclass) atParamMap.set(name, param);
2067
- return param;
2068
- }
2069
- // Handle @param with default: ['default', ['.', 'this', 'name'], value]
2070
- if (this.is(p, 'default') && this.is(p[1], '.') && p[1][1] === 'this') {
2071
- let name = p[1][2];
2072
- let param = isSubclass ? `_${name}` : name;
2073
- autoAssign.push(`this.${name} = ${param}`);
2074
- if (isSubclass) atParamMap.set(name, param);
2075
- return ['default', param, p[2]];
2076
- }
2077
- return p;
2078
- });
2079
- for (let bm of boundMethods) autoAssign.unshift(`this.${bm} = this.${bm}.bind(this)`);
2080
- if (atParamMap?.size > 0) this._atParamMap = atParamMap;
2081
- }
2082
- let pList = this.generateParamList(cleanParams);
2083
- let prefix = (isStatic ? 'static ' : '') + (hasAwait ? 'async ' : '') + (hasYield ? '*' : '');
2084
- code += this.indent() + `${prefix}${mName}(${pList}) `;
2085
- if (!isComputed) this.currentMethodName = mName;
2086
- code += this.generateMethodBody(body, autoAssign, mName === 'constructor', cleanParams);
2087
- this._atParamMap = null;
2088
- this.currentMethodName = null;
2089
- code += '\n';
2090
- } else if (isStatic) {
2091
- code += this.indent() + `static ${mName} = ${this.generate(mv, 'value')};\n`;
2092
- } else {
2093
- code += this.indent() + `${mName} = ${this.generate(mv, 'value')};\n`;
2094
- }
2095
- }
2096
- this.indentLevel--;
2097
- } else if (hasObjFirst) {
2053
+ if (hasObjFirst) {
2098
2054
  let members = bodyStmts[0].slice(1);
2099
- let additionalStmts = bodyStmts.slice(1);
2100
2055
  this.indentLevel++;
2101
- for (let [, mk, mv] of members) {
2102
- let isStatic = this.is(mk, '.') && mk[1] === 'this', mName = this.extractMemberName(mk);
2103
- if ((this.is(mv, '->') || this.is(mv, '=>'))) {
2104
- let [, params, body] = mv;
2105
- let pList = this.generateParamList(params);
2106
- let prefix = (isStatic ? 'static ' : '') + (this.containsAwait(body) ? 'async ' : '') + (this.containsYield(body) ? '*' : '');
2107
- code += this.indent() + `${prefix}${mName}(${pList}) `;
2108
- this.currentMethodName = mName;
2109
- code += this.generateMethodBody(body, [], mName === 'constructor', params);
2110
- this.currentMethodName = null;
2111
- code += '\n';
2112
- } else if (isStatic) {
2113
- code += this.indent() + `static ${mName} = ${this.generate(mv, 'value')};\n`;
2114
- } else {
2115
- code += this.indent() + `${mName} = ${this.generate(mv, 'value')};\n`;
2116
- }
2117
- }
2118
- for (let stmt of additionalStmts) {
2056
+ code += this._emitClassMembers(members, parentClass);
2057
+ for (let stmt of bodyStmts.slice(1)) {
2119
2058
  if (this.is(stmt, 'class')) {
2120
2059
  let [, nestedName, parent, ...nestedBody] = stmt;
2121
2060
  if (this.is(nestedName, '.') && nestedName[1] === 'this') {
2122
- code += this.indent() + `static ${nestedName[2]} = ${this.generate(['class', null, parent, ...nestedBody], 'value')};\n`;
2061
+ code += this.indent() + `static ${nestedName[2]} = ${this.emit(['class', null, parent, ...nestedBody], 'value')};\n`;
2062
+ } else {
2063
+ code += this.indent() + this.emit(stmt, 'statement') + ';\n';
2123
2064
  }
2124
2065
  } else {
2125
- code += this.indent() + this.generate(stmt, 'statement') + ';\n';
2066
+ code += this.indent() + this.emit(stmt, 'statement') + ';\n';
2126
2067
  }
2127
2068
  }
2128
2069
  this.indentLevel--;
@@ -2130,9 +2071,9 @@ export class CodeGenerator {
2130
2071
  this.indentLevel++;
2131
2072
  for (let stmt of bodyStmts) {
2132
2073
  if (this.is(stmt, '=') && Array.isArray(stmt[1]) && stmt[1][0] === '.' && stmt[1][1] === 'this') {
2133
- code += this.indent() + `static ${stmt[1][2]} = ${this.generate(stmt[2], 'value')};\n`;
2074
+ code += this.indent() + `static ${stmt[1][2]} = ${this.emit(stmt[2], 'value')};\n`;
2134
2075
  } else {
2135
- code += this.indent() + this.generate(stmt, 'statement') + ';\n';
2076
+ code += this.indent() + this.emit(stmt, 'statement') + ';\n';
2136
2077
  }
2137
2078
  }
2138
2079
  this.indentLevel--;
@@ -2144,12 +2085,75 @@ export class CodeGenerator {
2144
2085
  return code;
2145
2086
  }
2146
2087
 
2147
- generateSuper(head, rest) {
2088
+ _emitClassMembers(members, parentClass) {
2089
+ let code = '';
2090
+
2091
+ let boundMethods = [];
2092
+ for (let [, mk, mv] of members) {
2093
+ let isStatic = this.is(mk, '.') && mk[1] === 'this';
2094
+ let isComputed = this.is(mk, 'computed');
2095
+ let mName = this.extractMemberName(mk);
2096
+ if (this.is(mv, '=>') && !isStatic && !isComputed && mName !== 'constructor') boundMethods.push(mName);
2097
+ }
2098
+
2099
+ for (let [, mk, mv] of members) {
2100
+ let isStatic = this.is(mk, '.') && mk[1] === 'this';
2101
+ let isComputed = this.is(mk, 'computed');
2102
+ let mName = this.extractMemberName(mk);
2103
+
2104
+ if (this.is(mv, '->') || this.is(mv, '=>')) {
2105
+ let [, params, body] = mv;
2106
+ let hasAwait = this.containsAwait(body), hasYield = this.containsYield(body);
2107
+ let cleanParams = params, autoAssign = [];
2108
+
2109
+ if (mName === 'constructor') {
2110
+ let isSubclass = !!parentClass;
2111
+ let atParamMap = isSubclass ? new Map() : null;
2112
+ cleanParams = params.map(p => {
2113
+ if (this.is(p, '.') && p[1] === 'this') {
2114
+ let name = p[2];
2115
+ let param = isSubclass ? `_${name}` : name;
2116
+ autoAssign.push(`this.${name} = ${param}`);
2117
+ if (isSubclass) atParamMap.set(name, param);
2118
+ return param;
2119
+ }
2120
+ if (this.is(p, 'default') && this.is(p[1], '.') && p[1][1] === 'this') {
2121
+ let name = p[1][2];
2122
+ let param = isSubclass ? `_${name}` : name;
2123
+ autoAssign.push(`this.${name} = ${param}`);
2124
+ if (isSubclass) atParamMap.set(name, param);
2125
+ return ['default', param, p[2]];
2126
+ }
2127
+ return p;
2128
+ });
2129
+ for (let bm of boundMethods) autoAssign.unshift(`this.${bm} = this.${bm}.bind(this)`);
2130
+ if (atParamMap?.size > 0) this._atParamMap = atParamMap;
2131
+ }
2132
+
2133
+ let pList = this.emitParamList(cleanParams);
2134
+ let prefix = (isStatic ? 'static ' : '') + (hasAwait ? 'async ' : '') + (hasYield ? '*' : '');
2135
+ code += this.indent() + `${prefix}${mName}(${pList}) `;
2136
+ if (!isComputed) this.currentMethodName = mName;
2137
+ code += this.emitMethodBody(body, autoAssign, mName === 'constructor', cleanParams);
2138
+ this._atParamMap = null;
2139
+ this.currentMethodName = null;
2140
+ code += '\n';
2141
+ } else if (isStatic) {
2142
+ code += this.indent() + `static ${mName} = ${this.emit(mv, 'value')};\n`;
2143
+ } else {
2144
+ code += this.indent() + `${mName} = ${this.emit(mv, 'value')};\n`;
2145
+ }
2146
+ }
2147
+
2148
+ return code;
2149
+ }
2150
+
2151
+ emitSuper(head, rest) {
2148
2152
  if (rest.length === 0) {
2149
2153
  if (this.currentMethodName && this.currentMethodName !== 'constructor') return `super.${this.currentMethodName}()`;
2150
2154
  return 'super';
2151
2155
  }
2152
- let args = rest.map(a => this.unwrap(this.generate(a, 'value'))).join(', ');
2156
+ let args = rest.map(a => this.unwrap(this.emit(a, 'value'))).join(', ');
2153
2157
  if (this.currentMethodName && this.currentMethodName !== 'constructor') return `super.${this.currentMethodName}(${args})`;
2154
2158
  return `super(${args})`;
2155
2159
  }
@@ -2158,9 +2162,9 @@ export class CodeGenerator {
2158
2162
  // Modules
2159
2163
  // ---------------------------------------------------------------------------
2160
2164
 
2161
- generateImport(head, rest, context, sexpr) {
2165
+ emitImport(head, rest, context, sexpr) {
2162
2166
  if (rest.length === 1) {
2163
- let importExpr = `import(${this.generate(rest[0], 'value')})`;
2167
+ let importExpr = `import(${this.emit(rest[0], 'value')})`;
2164
2168
  if (meta(sexpr[0], 'await') === true) return `(await ${importExpr})`;
2165
2169
  return importExpr;
2166
2170
  }
@@ -2180,51 +2184,61 @@ export class CodeGenerator {
2180
2184
  let names = specifier.map(i => Array.isArray(i) && i.length === 2 ? `${i[0]} as ${i[1]}` : i).join(', ');
2181
2185
  return `import { ${names} } from ${fixedSource}`;
2182
2186
  }
2183
- return `import ${this.generate(specifier, 'value')} from ${fixedSource}`;
2187
+ return `import ${this.emit(specifier, 'value')} from ${fixedSource}`;
2184
2188
  }
2185
2189
 
2186
- generateExport(head, rest) {
2190
+ emitExport(head, rest) {
2187
2191
  let [decl] = rest;
2188
2192
  if (this.options.skipExports) {
2189
2193
  if (this.is(decl, '=')) {
2190
2194
  const prev = this._componentName;
2191
- if (this.is(decl[2], 'component')) this._componentName = str(decl[1]);
2192
- const result = `const ${decl[1]} = ${this.generate(decl[2], 'value')}`;
2195
+ const prevTP = this._componentTypeParams;
2196
+ if (this.is(decl[2], 'component')) {
2197
+ this._componentName = str(decl[1]);
2198
+ this._componentTypeParams = decl[1]?.typeParams || '';
2199
+ }
2200
+ const result = `const ${decl[1]} = ${this.emit(decl[2], 'value')}`;
2193
2201
  this._componentName = prev;
2202
+ this._componentTypeParams = prevTP;
2194
2203
  return result;
2195
2204
  }
2196
2205
  if (Array.isArray(decl) && decl.every(i => typeof i === 'string')) return '';
2197
- return this.generate(decl, 'statement');
2206
+ return this.emit(decl, 'statement');
2198
2207
  }
2199
2208
  if (this.is(decl, '=')) {
2200
2209
  const prev = this._componentName;
2201
- if (this.is(decl[2], 'component')) this._componentName = str(decl[1]);
2202
- const result = `export const ${decl[1]} = ${this.generate(decl[2], 'value')}`;
2210
+ const prevTP = this._componentTypeParams;
2211
+ if (this.is(decl[2], 'component')) {
2212
+ this._componentName = str(decl[1]);
2213
+ this._componentTypeParams = decl[1]?.typeParams || '';
2214
+ }
2215
+ const result = `export const ${decl[1]} = ${this.emit(decl[2], 'value')}`;
2203
2216
  this._componentName = prev;
2217
+ this._componentTypeParams = prevTP;
2204
2218
  return result;
2205
2219
  }
2206
2220
  if (Array.isArray(decl) && decl.every(i => typeof i === 'string')) return `export { ${decl.join(', ')} }`;
2207
- return `export ${this.generate(decl, 'statement')}`;
2221
+ return `export ${this.emit(decl, 'statement')}`;
2208
2222
  }
2209
2223
 
2210
- generateExportDefault(head, rest) {
2224
+ emitExportDefault(head, rest) {
2211
2225
  let [expr] = rest;
2212
2226
  if (this.options.skipExports) {
2213
- if (this.is(expr, '=')) return `const ${expr[1]} = ${this.generate(expr[2], 'value')}`;
2214
- return this.generate(expr, 'statement');
2227
+ if (this.is(expr, '=')) return `const ${expr[1]} = ${this.emit(expr[2], 'value')}`;
2228
+ return this.emit(expr, 'statement');
2215
2229
  }
2216
2230
  if (this.is(expr, '=')) {
2217
- return `const ${expr[1]} = ${this.generate(expr[2], 'value')};\nexport default ${expr[1]}`;
2231
+ return `const ${expr[1]} = ${this.emit(expr[2], 'value')};\nexport default ${expr[1]}`;
2218
2232
  }
2219
- return `export default ${this.generate(expr, 'statement')}`;
2233
+ return `export default ${this.emit(expr, 'statement')}`;
2220
2234
  }
2221
2235
 
2222
- generateExportAll(head, rest) {
2236
+ emitExportAll(head, rest) {
2223
2237
  if (this.options.skipExports) return '';
2224
2238
  return `export * from ${this.addJsExtensionAndAssertions(rest[0])}`;
2225
2239
  }
2226
2240
 
2227
- generateExportFrom(head, rest) {
2241
+ emitExportFrom(head, rest) {
2228
2242
  if (this.options.skipExports) return '';
2229
2243
  let [specifiers, source] = rest;
2230
2244
  let fixedSource = this.addJsExtensionAndAssertions(source);
@@ -2239,24 +2253,24 @@ export class CodeGenerator {
2239
2253
  // Special forms
2240
2254
  // ---------------------------------------------------------------------------
2241
2255
 
2242
- generateDoIIFE(head, rest) {
2243
- return `(${this.generate(rest[0], 'statement')})()`;
2256
+ emitDoIIFE(head, rest) {
2257
+ return `(${this.emit(rest[0], 'statement')})()`;
2244
2258
  }
2245
2259
 
2246
- generateRegex(head, rest) {
2247
- return rest.length === 0 ? head : this.generate(rest[0], 'value');
2260
+ emitRegex(head, rest) {
2261
+ return rest.length === 0 ? head : this.emit(rest[0], 'value');
2248
2262
  }
2249
2263
 
2250
- generateTaggedTemplate(head, rest) {
2264
+ emitTaggedTemplate(head, rest) {
2251
2265
  let [tag, s] = rest;
2252
- let tagCode = this.generate(tag, 'value');
2253
- let content = this.generate(s, 'value');
2266
+ let tagCode = this.emit(tag, 'value');
2267
+ let content = this.emit(s, 'value');
2254
2268
  if (content.startsWith('`')) return `${tagCode}${content}`;
2255
2269
  if (content.startsWith('"') || content.startsWith("'")) return `${tagCode}\`${content.slice(1, -1)}\``;
2256
2270
  return `${tagCode}\`${content}\``;
2257
2271
  }
2258
2272
 
2259
- generateString(head, rest) {
2273
+ emitString(head, rest) {
2260
2274
  let result = '`';
2261
2275
  for (let part of rest) {
2262
2276
  if (part instanceof String) {
@@ -2271,10 +2285,10 @@ export class CodeGenerator {
2271
2285
  } else if (Array.isArray(part)) {
2272
2286
  if (part.length === 1 && typeof part[0] === 'string' && !Array.isArray(part[0])) {
2273
2287
  let v = part[0];
2274
- result += /^[\d"']/.test(v) ? '${' + this.generate(v, 'value') + '}' : '${' + v + '}';
2288
+ result += /^[\d"']/.test(v) ? '${' + this.emit(v, 'value') + '}' : '${' + v + '}';
2275
2289
  } else {
2276
2290
  let expr = part.length === 1 && Array.isArray(part[0]) ? part[0] : part;
2277
- result += '${' + this.generate(expr, 'value') + '}';
2291
+ result += '${' + this.emit(expr, 'value') + '}';
2278
2292
  }
2279
2293
  }
2280
2294
  }
@@ -2304,9 +2318,24 @@ export class CodeGenerator {
2304
2318
  return val;
2305
2319
  }
2306
2320
 
2307
- generateDestructuringPattern(pattern) { return this.formatParam(pattern); }
2321
+ _tryPostfixCall(head, rest, context) {
2322
+ if (context !== 'statement' || rest.length !== 1) return null;
2323
+ let cond = this.findPostfixConditional(rest[0]);
2324
+ if (!cond) return null;
2325
+ let argWithout = this.rebuildWithoutConditional(cond);
2326
+ let calleeCode = this.emit(head, 'value');
2327
+ let condCode = this.emit(cond.condition, 'value');
2328
+ let valCode = this.emit(argWithout, 'value');
2329
+ return `if (${condCode}) ${calleeCode}(${valCode})`;
2330
+ }
2331
+
2332
+ _emitArgs(rest) {
2333
+ return rest.map(arg => this.unwrap(this.emit(arg, 'value'))).join(', ');
2334
+ }
2308
2335
 
2309
- generateParamList(params) {
2336
+ emitDestructuringPattern(pattern) { return this.formatParam(pattern); }
2337
+
2338
+ emitParamList(params) {
2310
2339
  let expIdx = params.findIndex(p => this.is(p, 'expansion'));
2311
2340
  if (expIdx !== -1) {
2312
2341
  let before = params.slice(0, expIdx), after = params.slice(expIdx + 1);
@@ -2330,14 +2359,14 @@ export class CodeGenerator {
2330
2359
  if (typeof param === 'string') return param;
2331
2360
  if (param instanceof String) return param.valueOf();
2332
2361
  if (this.is(param, 'rest')) return `...${param[1]}`;
2333
- if (this.is(param, 'default')) return `${param[1]} = ${this.generate(param[2], 'value')}`;
2362
+ if (this.is(param, 'default')) return `${param[1]} = ${this.emit(param[2], 'value')}`;
2334
2363
  if (this.is(param, '.') && param[1] === 'this') return param[2];
2335
2364
  if (this.is(param, 'array')) {
2336
2365
  let els = param.slice(1).map(el => {
2337
2366
  if (el === ',') return '';
2338
2367
  if (el === '...') return '';
2339
2368
  if (this.is(el, '...')) return `...${el[1]}`;
2340
- if (this.is(el, '=') && typeof el[1] === 'string') return `${el[1]} = ${this.generate(el[2], 'value')}`;
2369
+ if (this.is(el, '=') && typeof el[1] === 'string') return `${el[1]} = ${this.emit(el[2], 'value')}`;
2341
2370
  if (typeof el === 'string') return el;
2342
2371
  return this.formatParam(el);
2343
2372
  });
@@ -2346,9 +2375,9 @@ export class CodeGenerator {
2346
2375
  if (this.is(param, 'object')) {
2347
2376
  let pairs = param.slice(1).map(pair => {
2348
2377
  if (this.is(pair, '...')) return `...${pair[1]}`;
2349
- if (this.is(pair, 'default')) return `${pair[1]} = ${this.generate(pair[2], 'value')}`;
2378
+ if (this.is(pair, 'default')) return `${pair[1]} = ${this.emit(pair[2], 'value')}`;
2350
2379
  let [operator, key, value] = pair;
2351
- if (operator === '=') return `${key} = ${this.generate(value, 'value')}`;
2380
+ if (operator === '=') return `${key} = ${this.emit(value, 'value')}`;
2352
2381
  if (key === value) return key;
2353
2382
  return `${key}: ${this.formatParam(value)}`;
2354
2383
  });
@@ -2361,7 +2390,7 @@ export class CodeGenerator {
2361
2390
  // Body generation
2362
2391
  // ---------------------------------------------------------------------------
2363
2392
 
2364
- generateBodyWithReturns(body, params = [], options = {}) {
2393
+ emitBodyWithReturns(body, params = [], options = {}) {
2365
2394
  let {sideEffectOnly = false, autoAssignments = [], isConstructor = false, hasExpansionParams = false} = options;
2366
2395
  let prevSEO = this.sideEffectOnly;
2367
2396
  this.sideEffectOnly = sideEffectOnly;
@@ -2413,9 +2442,19 @@ export class CodeGenerator {
2413
2442
  this.restMiddleParam = null;
2414
2443
  }
2415
2444
 
2445
+ // Classify variables: inline `let` at first assignment vs hoist to top
2446
+ let prevInlinePending = this._inlineVarsPending;
2447
+ let inlineVars = new Set();
2448
+ if (newVars.size > 0 && statements.length > 0) {
2449
+ let classified = this.classifyVarsForInlining(statements, newVars);
2450
+ inlineVars = classified.inlineVars;
2451
+ if (inlineVars.size > 0) this._inlineVarsPending = new Set(inlineVars);
2452
+ }
2453
+
2416
2454
  this.indentLevel++;
2417
2455
  let code = '{\n';
2418
- if (newVars.size > 0) code += this.indent() + `let ${Array.from(newVars).sort().join(', ')};\n`;
2456
+ let hoistVars = new Set([...newVars].filter(v => !inlineVars.has(v)));
2457
+ if (hoistVars.size > 0) code += this.indent() + `let ${Array.from(hoistVars).sort().join(', ')};\n`;
2419
2458
 
2420
2459
  let firstIsSuper = autoAssignments.length > 0 && statements.length > 0 &&
2421
2460
  Array.isArray(statements[0]) && statements[0][0] === 'super';
@@ -2427,7 +2466,7 @@ export class CodeGenerator {
2427
2466
 
2428
2467
  if (!isLast && h === 'comprehension') {
2429
2468
  let [, expr, iters, guards] = stmt;
2430
- code += this.indent() + this.generateComprehensionAsLoop(expr, iters, guards) + '\n';
2469
+ code += this.indent() + this.emitComprehensionAsLoop(expr, iters, guards) + '\n';
2431
2470
  return;
2432
2471
  }
2433
2472
 
@@ -2436,7 +2475,7 @@ export class CodeGenerator {
2436
2475
  let hasMulti = (b) => this.is(b, 'block') && b.length > 2;
2437
2476
  let hasCtrlStmt = this.hasStatementInBranch(thenB) || elseB.some(b => this.hasStatementInBranch(b));
2438
2477
  if (hasCtrlStmt || hasMulti(thenB) || elseB.some(hasMulti)) {
2439
- code += this.generateIfElseWithEarlyReturns(stmt) + '\n';
2478
+ code += this.emitIfElseWithEarlyReturns(stmt) + '\n';
2440
2479
  return;
2441
2480
  }
2442
2481
  }
@@ -2446,20 +2485,30 @@ export class CodeGenerator {
2446
2485
  if (typeof target === 'string' && Array.isArray(value)) {
2447
2486
  let vh = value[0];
2448
2487
  if (vh === 'comprehension' || vh === 'for-in') {
2488
+ // Comprehension targets with inline vars: declare before the loop
2489
+ if (this._inlineVarsPending?.delete(target)) code += this.indent() + `let ${target};\n`;
2449
2490
  this.comprehensionTarget = target;
2450
- code += this.generate(value, 'value');
2491
+ code += this.emit(value, 'value');
2451
2492
  this.comprehensionTarget = null;
2452
2493
  code += this.indent() + `return ${target};\n`;
2453
2494
  return;
2454
2495
  }
2455
2496
  }
2497
+ // Handle inline vars at last statement (auto-return): split into let + return
2498
+ if ((typeof target === 'string' || target instanceof String) && this._inlineVarsPending?.has(str(target))) {
2499
+ this._inlineVarsPending.delete(str(target));
2500
+ let assignCode = this.emit(stmt, 'statement');
2501
+ code += this.indent() + 'let ' + this.addSemicolon(stmt, assignCode) + '\n';
2502
+ code += this.indent() + `return ${str(target)};\n`;
2503
+ return;
2504
+ }
2456
2505
  }
2457
2506
 
2458
2507
  let needsReturn = !isConstructor && !sideEffectOnly && isLast &&
2459
2508
  !noRetStmts.includes(h) && !loopStmts.includes(h) &&
2460
2509
  !this.hasExplicitControlFlow(stmt);
2461
2510
  let ctx = needsReturn ? 'value' : 'statement';
2462
- let sc = this.generate(stmt, ctx);
2511
+ let sc = this.emit(stmt, ctx);
2463
2512
  if (needsReturn) code += this.indent() + 'return ' + sc + ';\n';
2464
2513
  else code += this.indent() + this.addSemicolon(stmt, sc) + '\n';
2465
2514
  });
@@ -2467,8 +2516,8 @@ export class CodeGenerator {
2467
2516
 
2468
2517
  if (firstIsSuper) {
2469
2518
  let isSuperOnly = statements.length === 1;
2470
- if (isSuperOnly && !isConstructor) code += this.indent() + 'return ' + this.generate(statements[0], 'value') + ';\n';
2471
- else code += this.indent() + this.generate(statements[0], 'statement') + ';\n';
2519
+ if (isSuperOnly && !isConstructor) code += this.indent() + 'return ' + this.emit(statements[0], 'value') + ';\n';
2520
+ else code += this.indent() + this.emit(statements[0], 'statement') + ';\n';
2472
2521
  for (let a of autoAssignments) code += this.indent() + a + ';\n';
2473
2522
  genStatements(statements.slice(1));
2474
2523
  } else {
@@ -2481,6 +2530,7 @@ export class CodeGenerator {
2481
2530
  if (!noRetStmts.includes(lastH)) code += this.indent() + 'return;\n';
2482
2531
  }
2483
2532
 
2533
+ this._inlineVarsPending = prevInlinePending;
2484
2534
  this.indentLevel--;
2485
2535
  code += this.indent() + '}';
2486
2536
  this.scopeStack.pop();
@@ -2494,33 +2544,33 @@ export class CodeGenerator {
2494
2544
  if (isConstructor && autoAssignments.length > 0) {
2495
2545
  // Constructor with @params as a single expression — need to emit autoAssignments
2496
2546
  let isSuper = Array.isArray(body) && body[0] === 'super';
2497
- let bodyCode = this.generate(body, 'statement');
2547
+ let bodyCode = this.emit(body, 'statement');
2498
2548
  let assigns = autoAssignments.map(a => `${a};`).join(' ');
2499
2549
  result = isSuper ? `{ ${bodyCode}; ${assigns} }` : `{ ${assigns} ${bodyCode}; }`;
2500
- } else if (isConstructor || this.hasExplicitControlFlow(body)) result = `{ ${this.generate(body, 'statement')}; }`;
2501
- else if (Array.isArray(body) && (noRetStmts.includes(body[0]) || loopStmts.includes(body[0]))) result = `{ ${this.generate(body, 'statement')}; }`;
2502
- else if (sideEffectOnly) result = `{ ${this.generate(body, 'statement')}; return; }`;
2503
- else result = `{ return ${this.generate(body, 'value')}; }`;
2550
+ } else if (isConstructor || this.hasExplicitControlFlow(body)) result = `{ ${this.emit(body, 'statement')}; }`;
2551
+ else if (Array.isArray(body) && (noRetStmts.includes(body[0]) || loopStmts.includes(body[0]))) result = `{ ${this.emit(body, 'statement')}; }`;
2552
+ else if (sideEffectOnly) result = `{ ${this.emit(body, 'statement')}; return; }`;
2553
+ else result = `{ return ${this.emit(body, 'value')}; }`;
2504
2554
  this.scopeStack.pop();
2505
2555
  return result;
2506
2556
  }
2507
2557
 
2508
- generateFunctionBody(body, params = [], sideEffectOnly = false) {
2509
- return this.generateBodyWithReturns(body, params, {sideEffectOnly, hasExpansionParams: this.expansionAfterParams?.length > 0});
2558
+ emitFunctionBody(body, params = [], sideEffectOnly = false) {
2559
+ return this.emitBodyWithReturns(body, params, {sideEffectOnly, hasExpansionParams: this.expansionAfterParams?.length > 0});
2510
2560
  }
2511
2561
 
2512
- generateMethodBody(body, autoAssignments = [], isConstructor = false, params = []) {
2513
- return this.generateBodyWithReturns(body, params, {autoAssignments, isConstructor});
2562
+ emitMethodBody(body, autoAssignments = [], isConstructor = false, params = []) {
2563
+ return this.emitBodyWithReturns(body, params, {autoAssignments, isConstructor});
2514
2564
  }
2515
2565
 
2516
- generateBlockWithReturns(block) {
2517
- if (!Array.isArray(block) || block[0] !== 'block') return this.generate(block, 'statement');
2566
+ emitBlockWithReturns(block) {
2567
+ if (!Array.isArray(block) || block[0] !== 'block') return this.emit(block, 'statement');
2518
2568
  let stmts = this.unwrapBlock(block);
2519
2569
  let lines = this.withIndent(() => stmts.map((stmt, i) => {
2520
2570
  let isLast = i === stmts.length - 1;
2521
2571
  let h = Array.isArray(stmt) ? stmt[0] : null;
2522
2572
  let needsReturn = isLast && !['return', 'throw', 'break', 'continue'].includes(h);
2523
- let code = this.generate(stmt, needsReturn ? 'value' : 'statement');
2573
+ let code = this.emit(stmt, needsReturn ? 'value' : 'statement');
2524
2574
  return needsReturn ? this.indent() + 'return ' + code + ';' : this.indent() + code + ';';
2525
2575
  }));
2526
2576
  return `{\n${lines.join('\n')}\n${this.indent()}}`;
@@ -2530,25 +2580,25 @@ export class CodeGenerator {
2530
2580
  // Loop body helpers
2531
2581
  // ---------------------------------------------------------------------------
2532
2582
 
2533
- generateLoopBody(body) {
2534
- if (!Array.isArray(body)) return `{ ${this.generate(body, 'statement')}; }`;
2583
+ emitLoopBody(body) {
2584
+ if (!Array.isArray(body)) return `{ ${this.emit(body, 'statement')}; }`;
2535
2585
  if (body[0] === 'block' || Array.isArray(body[0])) {
2536
2586
  let stmts = body[0] === 'block' ? body.slice(1) : body;
2537
2587
  let lines = this.withIndent(() => stmts.map(s => {
2538
2588
  if (this.is(s, 'comprehension')) {
2539
2589
  let [, expr, iters, guards] = s;
2540
- return this.indent() + this.generateComprehensionAsLoop(expr, iters, guards);
2590
+ return this.indent() + this.emitComprehensionAsLoop(expr, iters, guards);
2541
2591
  }
2542
- return this.indent() + this.addSemicolon(s, this.generate(s, 'statement'));
2592
+ return this.indent() + this.addSemicolon(s, this.emit(s, 'statement'));
2543
2593
  }));
2544
2594
  return `{\n${lines.join('\n')}\n${this.indent()}}`;
2545
2595
  }
2546
- return `{ ${this.generate(body, 'statement')}; }`;
2596
+ return `{ ${this.emit(body, 'statement')}; }`;
2547
2597
  }
2548
2598
 
2549
- generateLoopBodyWithGuard(body, guard) {
2550
- let guardCond = this.unwrap(this.generate(guard, 'value'));
2551
- if (!Array.isArray(body)) return `{ if (${guardCond}) ${this.generate(body, 'statement')}; }`;
2599
+ emitLoopBodyWithGuard(body, guard) {
2600
+ let guardCond = this.unwrap(this.emit(guard, 'value'));
2601
+ if (!Array.isArray(body)) return `{ if (${guardCond}) ${this.emit(body, 'statement')}; }`;
2552
2602
  if (body[0] === 'block' || Array.isArray(body[0])) {
2553
2603
  let stmts = body[0] === 'block' ? body.slice(1) : body;
2554
2604
  let loopIndent = this.withIndent(() => this.indent());
@@ -2562,14 +2612,14 @@ export class CodeGenerator {
2562
2612
  let close = this.withIndent(() => this.indent() + '}');
2563
2613
  return `{\n${loopIndent}${guardCode}${innerStmts.join('\n')}\n${close}\n${this.indent()}}`;
2564
2614
  }
2565
- return `{ if (${this.generate(guard, 'value')}) ${this.generate(body, 'statement')}; }`;
2615
+ return `{ if (${this.emit(guard, 'value')}) ${this.emit(body, 'statement')}; }`;
2566
2616
  }
2567
2617
 
2568
2618
  // ---------------------------------------------------------------------------
2569
2619
  // Comprehension helpers
2570
2620
  // ---------------------------------------------------------------------------
2571
2621
 
2572
- generateComprehensionWithTarget(expr, iterators, guards, targetVar) {
2622
+ emitComprehensionWithTarget(expr, iterators, guards, targetVar) {
2573
2623
  let code = '';
2574
2624
  code += this.indent() + `${targetVar} = [];\n`;
2575
2625
  let unwrappedExpr = (this.is(expr, 'block') && expr.length === 2) ? expr[1] : expr;
@@ -2582,10 +2632,10 @@ export class CodeGenerator {
2582
2632
  this.indentLevel++;
2583
2633
  if (setup) code += this.indent() + setup + '\n';
2584
2634
  if (guards && guards.length > 0) {
2585
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2635
+ code += this.indent() + `if (${guards.map(g => this.emit(g, 'value')).join(' && ')}) {\n`;
2586
2636
  this.indentLevel++;
2587
2637
  }
2588
- code += this.indent() + `${targetVar}.push(${this.unwrap(this.generate(unwrappedExpr, 'value'))});\n`;
2638
+ code += this.indent() + `${targetVar}.push(${this.unwrap(this.emit(unwrappedExpr, 'value'))});\n`;
2589
2639
  if (guards && guards.length > 0) { this.indentLevel--; code += this.indent() + '}\n'; }
2590
2640
  this.indentLevel--;
2591
2641
  code += this.indent() + '}\n';
@@ -2595,19 +2645,19 @@ export class CodeGenerator {
2595
2645
  return this.indent() + `${targetVar} = (() => { /* complex comprehension */ })();\n`;
2596
2646
  }
2597
2647
 
2598
- generateComprehensionAsLoop(expr, iterators, guards) {
2648
+ emitComprehensionAsLoop(expr, iterators, guards) {
2599
2649
  let code = '';
2600
- let guardCond = guards?.length ? guards.map(g => this.generate(g, 'value')).join(' && ') : null;
2650
+ let guardCond = guards?.length ? guards.map(g => this.emit(g, 'value')).join(' && ') : null;
2601
2651
 
2602
2652
  // Helper: emit the loop body with optional guard wrapping
2603
2653
  let emitBody = () => {
2604
2654
  if (guardCond) {
2605
2655
  code += this.indent() + `if (${guardCond}) {\n`;
2606
2656
  this.indentLevel++;
2607
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2657
+ code += this.indent() + this.emit(expr, 'statement') + ';\n';
2608
2658
  this.indentLevel--; code += this.indent() + '}\n';
2609
2659
  } else {
2610
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2660
+ code += this.indent() + this.emit(expr, 'statement') + ';\n';
2611
2661
  }
2612
2662
  };
2613
2663
 
@@ -2648,38 +2698,38 @@ export class CodeGenerator {
2648
2698
  }
2649
2699
  }
2650
2700
 
2651
- return this.generate(['comprehension', expr, iterators, guards], 'value');
2701
+ return this.emit(['comprehension', expr, iterators, guards], 'value');
2652
2702
  }
2653
2703
 
2654
2704
  // ---------------------------------------------------------------------------
2655
2705
  // If/switch expression helpers
2656
2706
  // ---------------------------------------------------------------------------
2657
2707
 
2658
- generateIfElseWithEarlyReturns(ifStmt) {
2708
+ emitIfElseWithEarlyReturns(ifStmt) {
2659
2709
  let [head, condition, thenBranch, ...elseBranches] = ifStmt;
2660
2710
  let code = '';
2661
- let condCode = this.generate(condition, 'value');
2711
+ let condCode = this.emit(condition, 'value');
2662
2712
  code += this.indent() + `if (${condCode}) {\n`;
2663
- code += this.withIndent(() => this.generateBranchWithReturn(thenBranch));
2713
+ code += this.withIndent(() => this.emitBranchWithReturn(thenBranch));
2664
2714
  code += this.indent() + '}';
2665
2715
  for (let branch of elseBranches) {
2666
2716
  code += ' else ';
2667
2717
  if (this.is(branch, 'if')) {
2668
2718
  let [, nc, nt, ...ne] = branch;
2669
- code += `if (${this.generate(nc, 'value')}) {\n`;
2670
- code += this.withIndent(() => this.generateBranchWithReturn(nt));
2719
+ code += `if (${this.emit(nc, 'value')}) {\n`;
2720
+ code += this.withIndent(() => this.emitBranchWithReturn(nt));
2671
2721
  code += this.indent() + '}';
2672
- for (let rb of ne) { code += ' else {\n'; code += this.withIndent(() => this.generateBranchWithReturn(rb)); code += this.indent() + '}'; }
2722
+ for (let rb of ne) { code += ' else {\n'; code += this.withIndent(() => this.emitBranchWithReturn(rb)); code += this.indent() + '}'; }
2673
2723
  } else {
2674
2724
  code += '{\n';
2675
- code += this.withIndent(() => this.generateBranchWithReturn(branch));
2725
+ code += this.withIndent(() => this.emitBranchWithReturn(branch));
2676
2726
  code += this.indent() + '}';
2677
2727
  }
2678
2728
  }
2679
2729
  return code;
2680
2730
  }
2681
2731
 
2682
- generateBranchWithReturn(branch) {
2732
+ emitBranchWithReturn(branch) {
2683
2733
  branch = this.unwrapIfBranch(branch);
2684
2734
  let stmts = this.unwrapBlock(branch);
2685
2735
  let code = '';
@@ -2687,68 +2737,69 @@ export class CodeGenerator {
2687
2737
  let isLast = i === stmts.length - 1, s = stmts[i];
2688
2738
  let h = Array.isArray(s) ? s[0] : null;
2689
2739
  let hasCtrl = h === 'return' || h === 'throw' || h === 'break' || h === 'continue';
2690
- if (isLast && !hasCtrl) code += this.indent() + `return ${this.generate(s, 'value')};\n`;
2691
- else code += this.indent() + this.generate(s, 'statement') + ';\n';
2740
+ if (isLast && !hasCtrl) code += this.indent() + `return ${this.emit(s, 'value')};\n`;
2741
+ else code += this.indent() + this.emit(s, 'statement') + ';\n';
2692
2742
  }
2693
2743
  return code;
2694
2744
  }
2695
2745
 
2696
- generateIfAsExpression(condition, thenBranch, elseBranches) {
2746
+ emitIfAsExpression(condition, thenBranch, elseBranches) {
2697
2747
  let needsIIFE = this.is(thenBranch, 'block') && thenBranch.length > 2 || this.hasStatementInBranch(thenBranch) ||
2698
2748
  elseBranches.some(b => this.is(b, 'block') && b.length > 2 || this.hasStatementInBranch(b) || this.hasNestedMultiStatement(b));
2699
2749
  if (needsIIFE) {
2750
+ // Enclosed: condition, thenBranch, elseBranches
2700
2751
  let hasAwait = this.containsAwait(condition) || this.containsAwait(thenBranch) || elseBranches.some(b => this.containsAwait(b));
2701
- let code = `${hasAwait ? 'await ' : ''}(${hasAwait ? 'async ' : ''}() => { `;
2702
- code += `if (${this.generate(condition, 'value')}) `;
2703
- code += this.generateBlockWithReturns(thenBranch);
2752
+ let code = this.asyncIIFEOpen(hasAwait) + ' ';
2753
+ code += `if (${this.emit(condition, 'value')}) `;
2754
+ code += this.emitBlockWithReturns(thenBranch);
2704
2755
  for (let branch of elseBranches) {
2705
2756
  code += ' else ';
2706
2757
  if (this.is(branch, 'if')) {
2707
2758
  let [_, nc, nt, ...ne] = branch;
2708
- code += `if (${this.generate(nc, 'value')}) `;
2709
- code += this.generateBlockWithReturns(nt);
2759
+ code += `if (${this.emit(nc, 'value')}) `;
2760
+ code += this.emitBlockWithReturns(nt);
2710
2761
  for (let nb of ne) {
2711
2762
  code += ' else ';
2712
2763
  if (this.is(nb, 'if')) {
2713
2764
  let [__, nnc, nnt, ...nne] = nb;
2714
- code += `if (${this.generate(nnc, 'value')}) `;
2715
- code += this.generateBlockWithReturns(nnt);
2765
+ code += `if (${this.emit(nnc, 'value')}) `;
2766
+ code += this.emitBlockWithReturns(nnt);
2716
2767
  elseBranches.push(...nne);
2717
2768
  } else {
2718
- code += this.generateBlockWithReturns(nb);
2769
+ code += this.emitBlockWithReturns(nb);
2719
2770
  }
2720
2771
  }
2721
2772
  } else {
2722
- code += this.generateBlockWithReturns(branch);
2773
+ code += this.emitBlockWithReturns(branch);
2723
2774
  }
2724
2775
  }
2725
2776
  return code + ' })()';
2726
2777
  }
2727
2778
  let thenExpr = this.extractExpression(this.unwrapIfBranch(thenBranch));
2728
2779
  let elseExpr = this.buildTernaryChain(elseBranches);
2729
- let condCode = this.generate(condition, 'value');
2780
+ let condCode = this.emit(condition, 'value');
2730
2781
  if ((this.is(condition, 'yield') || this.is(condition, 'await'))) condCode = `(${condCode})`;
2731
2782
  return `(${condCode} ? ${thenExpr} : ${elseExpr})`;
2732
2783
  }
2733
2784
 
2734
- generateIfAsStatement(condition, thenBranch, elseBranches) {
2735
- let code = `if (${this.unwrap(this.generate(condition, 'value'))}) `;
2736
- code += this.generate(this.unwrapIfBranch(thenBranch), 'statement');
2737
- for (let branch of elseBranches) code += ` else ` + this.generate(this.unwrapIfBranch(branch), 'statement');
2785
+ emitIfAsStatement(condition, thenBranch, elseBranches) {
2786
+ let code = `if (${this.unwrap(this.emit(condition, 'value'))}) `;
2787
+ code += this.emit(this.unwrapIfBranch(thenBranch), 'statement');
2788
+ for (let branch of elseBranches) code += ` else ` + this.emit(this.unwrapIfBranch(branch), 'statement');
2738
2789
  return code;
2739
2790
  }
2740
2791
 
2741
- generateSwitchCaseBody(body, context) {
2792
+ emitSwitchCaseBody(body, context) {
2742
2793
  let code = '';
2743
2794
  let hasFlow = this.hasExplicitControlFlow(body);
2744
2795
  let stmts = this.unwrapBlock(body);
2745
2796
  if (hasFlow) {
2746
- for (let s of stmts) code += this.indent() + this.generate(s, 'statement') + ';\n';
2797
+ for (let s of stmts) code += this.indent() + this.emit(s, 'statement') + ';\n';
2747
2798
  } else if (context === 'value') {
2748
2799
  if (this.is(body, 'block') && body.length > 2) {
2749
2800
  for (let i = 0; i < stmts.length; i++) {
2750
- if (i === stmts.length - 1) code += this.indent() + `return ${this.generate(stmts[i], 'value')};\n`;
2751
- else code += this.indent() + this.generate(stmts[i], 'statement') + ';\n';
2801
+ if (i === stmts.length - 1) code += this.indent() + `return ${this.emit(stmts[i], 'value')};\n`;
2802
+ else code += this.indent() + this.emit(stmts[i], 'statement') + ';\n';
2752
2803
  }
2753
2804
  } else {
2754
2805
  code += this.indent() + `return ${this.extractExpression(body)};\n`;
@@ -2758,26 +2809,26 @@ export class CodeGenerator {
2758
2809
  let [_, condition, thenBranch, ...elseBranches] = stmts[0];
2759
2810
  let thenExpr = this.extractExpression(this.unwrapIfBranch(thenBranch));
2760
2811
  let elseExpr = this.buildTernaryChain(elseBranches);
2761
- code += this.indent() + `(${this.unwrap(this.generate(condition, 'value'))} ? ${thenExpr} : ${elseExpr});\n`;
2812
+ code += this.indent() + `(${this.unwrap(this.emit(condition, 'value'))} ? ${thenExpr} : ${elseExpr});\n`;
2762
2813
  } else if (this.is(body, 'block') && body.length > 1) {
2763
- for (let s of stmts) code += this.indent() + this.generate(s, 'statement') + ';\n';
2814
+ for (let s of stmts) code += this.indent() + this.emit(s, 'statement') + ';\n';
2764
2815
  } else {
2765
- code += this.indent() + this.generate(body, 'statement') + ';\n';
2816
+ code += this.indent() + this.emit(body, 'statement') + ';\n';
2766
2817
  }
2767
2818
  code += this.indent() + 'break;\n';
2768
2819
  }
2769
2820
  return code;
2770
2821
  }
2771
2822
 
2772
- generateSwitchAsIfChain(whens, defaultCase, context) {
2823
+ emitSwitchAsIfChain(whens, defaultCase, context) {
2773
2824
  let code = '';
2774
2825
  for (let i = 0; i < whens.length; i++) {
2775
2826
  let [, test, body] = whens[i];
2776
2827
  let cond = Array.isArray(test) ? test[0] : test;
2777
- code += (i === 0 ? '' : ' else ') + `if (${this.generate(cond, 'value')}) {\n`;
2828
+ code += (i === 0 ? '' : ' else ') + `if (${this.emit(cond, 'value')}) {\n`;
2778
2829
  this.indentLevel++;
2779
2830
  if (context === 'value') code += this.indent() + `return ${this.extractExpression(body)};\n`;
2780
- else for (let s of this.unwrapBlock(body)) code += this.indent() + this.generate(s, 'statement') + ';\n';
2831
+ else for (let s of this.unwrapBlock(body)) code += this.indent() + this.emit(s, 'statement') + ';\n';
2781
2832
  this.indentLevel--;
2782
2833
  code += this.indent() + '}';
2783
2834
  }
@@ -2785,11 +2836,31 @@ export class CodeGenerator {
2785
2836
  code += ' else {\n';
2786
2837
  this.indentLevel++;
2787
2838
  if (context === 'value') code += this.indent() + `return ${this.extractExpression(defaultCase)};\n`;
2788
- else for (let s of this.unwrapBlock(defaultCase)) code += this.indent() + this.generate(s, 'statement') + ';\n';
2839
+ else for (let s of this.unwrapBlock(defaultCase)) code += this.indent() + this.emit(s, 'statement') + ';\n';
2789
2840
  this.indentLevel--;
2790
2841
  code += this.indent() + '}';
2791
2842
  }
2792
- return context === 'value' ? `(() => { ${code} })()` : code;
2843
+ if (context === 'value') {
2844
+ let hasAwait = whens.some(w => this.containsAwait(w[1]) || this.containsAwait(w[2])) || (defaultCase && this.containsAwait(defaultCase));
2845
+ return this.asyncIIFE(hasAwait, code);
2846
+ }
2847
+ return code;
2848
+ }
2849
+
2850
+ // ---------------------------------------------------------------------------
2851
+ // Async IIFE helpers
2852
+ // ---------------------------------------------------------------------------
2853
+
2854
+ asyncIIFE(hasAwait, body) {
2855
+ let prefix = hasAwait ? 'await ' : '';
2856
+ let async_ = hasAwait ? 'async ' : '';
2857
+ return `${prefix}(${async_}() => { ${body} })()`;
2858
+ }
2859
+
2860
+ asyncIIFEOpen(hasAwait) {
2861
+ let prefix = hasAwait ? 'await ' : '';
2862
+ let async_ = hasAwait ? 'async ' : '';
2863
+ return `${prefix}(${async_}() => {`;
2793
2864
  }
2794
2865
 
2795
2866
  // ---------------------------------------------------------------------------
@@ -2798,7 +2869,7 @@ export class CodeGenerator {
2798
2869
 
2799
2870
  extractExpression(branch) {
2800
2871
  let stmts = this.unwrapBlock(branch);
2801
- return stmts.length > 0 ? this.generate(stmts[stmts.length - 1], 'value') : 'undefined';
2872
+ return stmts.length > 0 ? this.emit(stmts[stmts.length - 1], 'value') : 'undefined';
2802
2873
  }
2803
2874
 
2804
2875
  unwrapBlock(body) {
@@ -2823,7 +2894,7 @@ export class CodeGenerator {
2823
2894
  addSemicolon(stmt, generated) { return generated + (this.needsSemicolon(stmt, generated) ? ';' : ''); }
2824
2895
 
2825
2896
  formatStatements(stmts, context = 'statement') {
2826
- return stmts.map(s => this.indent() + this.addSemicolon(s, this.generate(s, context)));
2897
+ return stmts.map(s => this.indent() + this.addSemicolon(s, this.emit(s, context)));
2827
2898
  }
2828
2899
 
2829
2900
  wrapForCondition(code) {
@@ -2968,7 +3039,7 @@ export class CodeGenerator {
2968
3039
  let [_, cond, then_, ...rest] = first;
2969
3040
  let thenPart = this.extractExpression(this.unwrapIfBranch(then_));
2970
3041
  let elsePart = this.buildTernaryChain([...rest, ...branches.slice(1)]);
2971
- return `(${this.generate(cond, 'value')} ? ${thenPart} : ${elsePart})`;
3042
+ return `(${this.emit(cond, 'value')} ? ${thenPart} : ${elsePart})`;
2972
3043
  }
2973
3044
  return this.extractExpression(this.unwrapIfBranch(first));
2974
3045
  }
@@ -3092,10 +3163,201 @@ export class CodeGenerator {
3092
3163
  return false;
3093
3164
  }
3094
3165
 
3166
+ // ---------------------------------------------------------------------------
3167
+ // Variable inlining — emit `let` at first assignment instead of hoisting
3168
+ // ---------------------------------------------------------------------------
3169
+
3170
+ // Check if an s-expression references a variable name (stopping at function boundaries)
3171
+ referencesVar(sexpr, varName) {
3172
+ if (!sexpr) return false;
3173
+ if (sexpr instanceof String) return str(sexpr) === varName;
3174
+ if (typeof sexpr === 'string') return sexpr === varName;
3175
+ if (!Array.isArray(sexpr)) return false;
3176
+ let h = sexpr[0];
3177
+ let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
3178
+ if (hs === 'def' || hs === '->' || hs === '=>' || hs === 'effect') return false;
3179
+ // Property access: only check the object, not the property name
3180
+ if (hs === '.' || hs === '?.') return this.referencesVar(sexpr[1], varName);
3181
+ // Object literal: check values but not simple string keys
3182
+ if (hs === 'object') {
3183
+ for (let i = 1; i < sexpr.length; i++) {
3184
+ let pair = sexpr[i];
3185
+ if (Array.isArray(pair)) {
3186
+ if (this.is(pair, '...')) { if (this.referencesVar(pair[1], varName)) return true; }
3187
+ else if (pair.length >= 2) { if (this.referencesVar(pair[pair.length - 1], varName)) return true; }
3188
+ } else { if (this.referencesVar(pair, varName)) return true; }
3189
+ }
3190
+ return false;
3191
+ }
3192
+ return sexpr.some(item => this.referencesVar(item, varName));
3193
+ }
3194
+
3195
+ // Check if the first reference to a variable in DFS order is a `=` assignment
3196
+ // at statement level (not inside a value expression like another assignment's RHS).
3197
+ // Returns true only when it's safe to emit `let` at the first assignment site.
3198
+ firstRefIsAssignment(sexpr, varName) {
3199
+ let result = null; // null = not found, 'write' = statement-level assignment, 'read' = read/value
3200
+ let isVar = (n) => (n instanceof String ? str(n) : n) === varName;
3201
+ let walk = (node, inValue) => {
3202
+ if (result !== null) return;
3203
+ if (!node) return;
3204
+ if (!Array.isArray(node)) { if (isVar(node)) result = 'read'; return; }
3205
+ let h = node[0];
3206
+ let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
3207
+ if (hs === 'def' || hs === '->' || hs === '=>' || hs === 'effect') return;
3208
+ // Assignment to our variable
3209
+ if (hs === '=' && (typeof node[1] === 'string' || node[1] instanceof String) && str(node[1]) === varName) {
3210
+ if (inValue) { result = 'read'; return; } // In value context — can't emit `let` here
3211
+ result = this.referencesVar(node[2], varName) ? 'read' : 'write';
3212
+ return;
3213
+ }
3214
+ // Compound assignment reads the variable
3215
+ if (CodeEmitter.ASSIGNMENT_OPS.has(hs) && hs !== '=' &&
3216
+ (typeof node[1] === 'string' || node[1] instanceof String) && str(node[1]) === varName) {
3217
+ result = 'read'; return;
3218
+ }
3219
+ // Property access: only check object
3220
+ if (hs === '.' || hs === '?.') { walk(node[1], inValue); return; }
3221
+ // Object literal: skip simple string keys
3222
+ if (hs === 'object') {
3223
+ for (let i = 1; i < node.length; i++) {
3224
+ let pair = node[i];
3225
+ if (Array.isArray(pair)) {
3226
+ if (this.is(pair, '...')) walk(pair[1], true);
3227
+ else if (pair.length >= 2) walk(pair[pair.length - 1], true);
3228
+ } else walk(pair, true);
3229
+ }
3230
+ return;
3231
+ }
3232
+ // Block: children are at statement level
3233
+ if (hs === 'block') { for (let i = 1; i < node.length; i++) walk(node[i], false); return; }
3234
+ // If: condition is value, branches maintain parent context
3235
+ if (hs === 'if') { walk(node[1], true); for (let i = 2; i < node.length; i++) walk(node[i], inValue); return; }
3236
+ // Loops: iterable/condition is value, body is statement
3237
+ if (hs === 'for-in' || hs === 'for-of' || hs === 'for-as') { walk(node[2], true); if (node.length > 3) walk(node[node.length - 1], false); return; }
3238
+ if (hs === 'while') { walk(node[1], true); walk(node[2], false); return; }
3239
+ // Try: blocks are statement level
3240
+ if (hs === 'try') { for (let i = 1; i < node.length; i++) walk(node[i], false); return; }
3241
+ // Other assignments: RHS is value context
3242
+ if (CodeEmitter.ASSIGNMENT_OPS.has(hs)) { walk(node[2], true); return; }
3243
+ // Default: everything else is value context
3244
+ for (let i = 0; i < node.length; i++) walk(node[i], true);
3245
+ };
3246
+ walk(sexpr, false); // top of a statement is statement context
3247
+ return result === 'write';
3248
+ }
3249
+
3250
+ // Check if all references to a variable within a statement are contained in a
3251
+ // single block child. Returns false if the variable appears at multiple nesting
3252
+ // levels (e.g., inside an if-branch AND at the same level as the if).
3253
+ allRefsInSingleBlock(sexpr, varName) {
3254
+ if (!Array.isArray(sexpr)) return false;
3255
+ let h = sexpr[0];
3256
+ let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
3257
+ // For 'if': check condition + each branch. Variable must appear in exactly one branch,
3258
+ // and NOT in the condition. Then recurse into that branch.
3259
+ if (hs === 'if') {
3260
+ if (this.referencesVar(sexpr[1], varName)) return false; // in condition
3261
+ let branchCount = 0;
3262
+ let refBranch = null;
3263
+ for (let i = 2; i < sexpr.length; i++) {
3264
+ if (this.referencesVar(sexpr[i], varName)) { branchCount++; refBranch = sexpr[i]; }
3265
+ }
3266
+ if (branchCount !== 1) return false;
3267
+ return this.allRefsInSingleBlock(refBranch, varName);
3268
+ }
3269
+ // For 'block': variable must appear in only one child statement.
3270
+ // Then recurse into that child.
3271
+ if (hs === 'block') {
3272
+ let childCount = 0;
3273
+ let refChild = null;
3274
+ for (let i = 1; i < sexpr.length; i++) {
3275
+ if (this.referencesVar(sexpr[i], varName)) { childCount++; refChild = sexpr[i]; }
3276
+ }
3277
+ if (childCount !== 1) return false;
3278
+ // If the single child IS a direct assignment to this variable, we've bottomed out — safe.
3279
+ if (Array.isArray(refChild) && refChild[0] === '=' &&
3280
+ (typeof refChild[1] === 'string' || refChild[1] instanceof String) &&
3281
+ str(refChild[1]) === varName) return true;
3282
+ return this.allRefsInSingleBlock(refChild, varName);
3283
+ }
3284
+ // For loops: iterable/condition must not reference var, body is a block — recurse.
3285
+ if (hs === 'for-in' || hs === 'for-of' || hs === 'for-as') {
3286
+ if (this.referencesVar(sexpr[2], varName)) return false;
3287
+ return true;
3288
+ }
3289
+ if (hs === 'while') {
3290
+ if (this.referencesVar(sexpr[1], varName)) return false;
3291
+ return true;
3292
+ }
3293
+ // For try: recurse
3294
+ if (hs === 'try') return true;
3295
+ return false;
3296
+ }
3297
+
3298
+ // Classify variables into those that can have `let` inlined at their first assignment
3299
+ // vs those that must be hoisted to the function/program top.
3300
+ classifyVarsForInlining(statements, vars) {
3301
+ if (vars.size === 0) return { inlineVars: new Set(), hoistVars: new Set(vars) };
3302
+
3303
+ // Pre-scan: variables with type annotations (::) must be hoisted. The DTS header
3304
+ // declares them as `let x: Type;` — inlining `let x = value;` in the body would
3305
+ // create a duplicate declaration, causing TS2454 ("used before being assigned").
3306
+ let typedVars = new Set();
3307
+ let findTypedAssigns = (node) => {
3308
+ if (!Array.isArray(node)) return;
3309
+ if (node[0] === '=' && node[1] instanceof String && node[1].type && vars.has(str(node[1]))) {
3310
+ typedVars.add(str(node[1]));
3311
+ }
3312
+ for (let i = 1; i < node.length; i++) findTypedAssigns(node[i]);
3313
+ };
3314
+ for (let stmt of statements) findTypedAssigns(stmt);
3315
+
3316
+ // For each variable, find which statement indices reference it
3317
+ let varStmts = new Map();
3318
+ for (let v of vars) varStmts.set(v, []);
3319
+ for (let i = 0; i < statements.length; i++) {
3320
+ for (let v of vars) {
3321
+ if (this.referencesVar(statements[i], v)) varStmts.get(v).push(i);
3322
+ }
3323
+ }
3324
+ let inlineVars = new Set(), hoistVars = new Set();
3325
+ for (let [v, indices] of varStmts) {
3326
+ if (indices.length === 0) { hoistVars.add(v); continue; }
3327
+ // Typed variables must be hoisted — DTS header already declares them
3328
+ if (typedVars.has(v)) { hoistVars.add(v); continue; }
3329
+ let firstIdx = indices[0];
3330
+ let firstStmt = statements[firstIdx];
3331
+ // Check if the first statement containing this variable is a direct `=` assignment to it
3332
+ let isDirectAssign = Array.isArray(firstStmt) && firstStmt[0] === '=' &&
3333
+ (typeof firstStmt[1] === 'string' || firstStmt[1] instanceof String) &&
3334
+ str(firstStmt[1]) === v &&
3335
+ !this.referencesVar(firstStmt[2], v);
3336
+ if (isDirectAssign) {
3337
+ // First reference is a direct body-level assignment — safe to inline `let` here
3338
+ // (same scope as hoisted `let`, but TS can infer the type from the initializer)
3339
+ inlineVars.add(v);
3340
+ } else if (indices.length === 1 && !this.is(firstStmt, 'switch') &&
3341
+ this.allRefsInSingleBlock(firstStmt, v) &&
3342
+ this.firstRefIsAssignment(firstStmt, v)) {
3343
+ // All references are within a single block branch of a non-switch statement
3344
+ // (e.g., all inside one if-branch, one for body, etc.) and the first DFS
3345
+ // reference is a direct assignment. JS 'let' block scoping is safe here
3346
+ // because the variable never escapes that single block.
3347
+ inlineVars.add(v);
3348
+ } else {
3349
+ // Variable spans multiple statements, is in a switch, or first ref is a read.
3350
+ // Must hoist to ensure correct scoping.
3351
+ hoistVars.add(v);
3352
+ }
3353
+ }
3354
+ return { inlineVars, hoistVars };
3355
+ }
3356
+
3095
3357
  // Class helpers
3096
3358
  extractMemberName(mk) {
3097
3359
  if (this.is(mk, '.') && mk[1] === 'this') return mk[2];
3098
- if (this.is(mk, 'computed')) return `[${this.generate(mk[1], 'value')}]`;
3360
+ if (this.is(mk, 'computed')) return `[${this.emit(mk[1], 'value')}]`;
3099
3361
  return mk;
3100
3362
  }
3101
3363
 
@@ -3361,7 +3623,12 @@ export class Compiler {
3361
3623
 
3362
3624
  // Step 1: Tokenize (includes rewriteTypes() via installTypeSupport)
3363
3625
  let lexer = new Lexer();
3364
- let tokens = lexer.tokenize(source);
3626
+ let tokens;
3627
+ try {
3628
+ tokens = lexer.tokenize(source);
3629
+ } catch (err) {
3630
+ throw toRipError(err, source, this.options.filename);
3631
+ }
3365
3632
  if (this.options.showTokens) {
3366
3633
  tokens.forEach(t => console.log(`${t[0].padEnd(12)} ${JSON.stringify(t[1])}`));
3367
3634
  console.log();
@@ -3377,6 +3644,46 @@ export class Compiler {
3377
3644
  // Remove TYPE_DECL markers — the parser doesn't know about them
3378
3645
  tokens = tokens.filter(t => t[0] !== 'TYPE_DECL');
3379
3646
 
3647
+ // Elide type-only imports — after type stripping, imported names that were
3648
+ // only used in type annotations no longer appear in the token stream.
3649
+ // Only elide when at least one name was consumed by type annotation stripping.
3650
+ if (lexer.typeRefNames?.size > 0) {
3651
+ let typeRefNames = lexer.typeRefNames;
3652
+ let usedNames = new Set();
3653
+ let inImport = false;
3654
+ for (let t of tokens) {
3655
+ if (t[0] === 'IMPORT') { inImport = true; continue; }
3656
+ if (inImport && t[0] === 'TERMINATOR') { inImport = false; continue; }
3657
+ if (inImport) continue;
3658
+ if (t[0] === 'IDENTIFIER') usedNames.add(t[1]);
3659
+ }
3660
+ for (let i = tokens.length - 1; i >= 0; i--) {
3661
+ if (tokens[i][0] !== 'IMPORT') continue;
3662
+ let j = i + 1;
3663
+ if (j >= tokens.length) continue;
3664
+ // Skip dynamic imports: import(expr)
3665
+ if (tokens[j][0] === 'CALL_START' || tokens[j][0] === '(') continue;
3666
+ // Skip side-effect imports: import 'module'
3667
+ if (tokens[j][0] === 'STRING') continue;
3668
+ // Collect imported names between IMPORT and FROM
3669
+ let names = [];
3670
+ while (j < tokens.length && tokens[j][0] !== 'FROM' && tokens[j][0] !== 'TERMINATOR') {
3671
+ if (tokens[j][0] === 'IDENTIFIER') names.push(tokens[j][1]);
3672
+ j++;
3673
+ }
3674
+ if (names.length === 0) continue;
3675
+ // Keep if any name is used at runtime
3676
+ if (names.some(n => usedNames.has(n))) continue;
3677
+ // Only elide if at least one name was used in a type annotation
3678
+ if (!names.some(n => typeRefNames.has(n))) continue;
3679
+ // All imported names are type-only — remove IMPORT through TERMINATOR
3680
+ let end = j;
3681
+ while (end < tokens.length && tokens[end][0] !== 'TERMINATOR') end++;
3682
+ if (end < tokens.length) end++; // include TERMINATOR
3683
+ tokens.splice(i, end - i);
3684
+ }
3685
+ }
3686
+
3380
3687
  // Strip leading terminators that may result from removed type declarations
3381
3688
  while (tokens.length > 0 && tokens[0][0] === 'TERMINATOR') {
3382
3689
  tokens.shift();
@@ -3389,6 +3696,7 @@ export class Compiler {
3389
3696
  }
3390
3697
 
3391
3698
  // Step 3: Parse — shim adapter wraps token values with metadata
3699
+ let lastLexedLoc = null;
3392
3700
  parser.lexer = {
3393
3701
  tokens, pos: 0,
3394
3702
  setInput: function() {},
@@ -3403,6 +3711,8 @@ export class Compiler {
3403
3711
  }
3404
3712
  this.text = val;
3405
3713
  this.loc = token.loc;
3714
+ this.line = token.loc?.r;
3715
+ lastLexedLoc = token.loc;
3406
3716
  return token[0];
3407
3717
  }
3408
3718
  };
@@ -3410,11 +3720,21 @@ export class Compiler {
3410
3720
  let sexpr;
3411
3721
  try {
3412
3722
  sexpr = parser.parse(source);
3413
- } catch (parseError) {
3723
+ } catch (err) {
3414
3724
  if (/\?\s*\([^)]*\?[^)]*:[^)]*\)\s*:/.test(source) || /\?\s+\w+\s+\?\s+/.test(source)) {
3415
- throw new Error('Nested ternary operators are not supported. Use if/else statements instead.');
3725
+ throw new RipError('Nested ternary operators are not supported', {
3726
+ code: 'E_PARSE', source, file: this.options.filename,
3727
+ suggestion: 'Use if/else statements instead.',
3728
+ phase: 'parser',
3729
+ });
3730
+ }
3731
+ let re = toRipError(err, source, this.options.filename);
3732
+ if (re.phase === 'parser' && lastLexedLoc) {
3733
+ re.line = lastLexedLoc.r ?? re.line;
3734
+ re.column = lastLexedLoc.c ?? re.column;
3735
+ re.length = lastLexedLoc.n || 1;
3416
3736
  }
3417
- throw parseError;
3737
+ throw re;
3418
3738
  }
3419
3739
 
3420
3740
  if (this.options.showSExpr) {
@@ -3430,9 +3750,10 @@ export class Compiler {
3430
3750
  sourceMap = new SourceMapGenerator(file, sourceFile, source);
3431
3751
  }
3432
3752
 
3433
- let generator = new CodeGenerator({
3753
+ let generator = new CodeEmitter({
3434
3754
  dataSection,
3435
3755
  source,
3756
+ filename: this.options.filename,
3436
3757
  skipPreamble: this.options.skipPreamble,
3437
3758
  skipRuntimes: this.options.skipRuntimes,
3438
3759
  skipExports: this.options.skipExports,
@@ -3469,13 +3790,13 @@ export class Compiler {
3469
3790
  // Component Support (prototype installation)
3470
3791
  // =============================================================================
3471
3792
 
3472
- installComponentSupport(CodeGenerator, Lexer);
3793
+ installComponentSupport(CodeEmitter, Lexer);
3473
3794
 
3474
3795
  // =============================================================================
3475
3796
  // Type Support (enum generator)
3476
3797
  // =============================================================================
3477
3798
 
3478
- CodeGenerator.prototype.generateEnum = generateEnum;
3799
+ CodeEmitter.prototype.emitEnum = emitEnum;
3479
3800
 
3480
3801
  // =============================================================================
3481
3802
  // Convenience Functions
@@ -3489,8 +3810,8 @@ export function compileToJS(source, options = {}) {
3489
3810
  return new Compiler(options).compileToJS(source);
3490
3811
  }
3491
3812
 
3492
- export function generate(sexpr, options = {}) {
3493
- return new CodeGenerator(options).compile(sexpr);
3813
+ export function emit(sexpr, options = {}) {
3814
+ return new CodeEmitter(options).compile(sexpr);
3494
3815
  }
3495
3816
 
3496
3817
  export function getStdlibCode() {
@@ -3512,11 +3833,12 @@ globalThis.zip ??= (...a) => a[0].map((_, i) => a.map(b => b[i]));
3512
3833
  }
3513
3834
 
3514
3835
  export function getReactiveRuntime() {
3515
- return new CodeGenerator({}).getReactiveRuntime();
3836
+ return new CodeEmitter({}).getReactiveRuntime();
3516
3837
  }
3517
3838
 
3518
3839
  export function getComponentRuntime() {
3519
- return new CodeGenerator({}).getComponentRuntime();
3840
+ return new CodeEmitter({}).getComponentRuntime();
3520
3841
  }
3521
3842
 
3522
3843
  export { formatSExpr };
3844
+ export { RipError, toRipError, formatError, formatErrorHTML } from './error.js';