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/CHANGELOG.md +0 -1
- package/README.md +4 -7
- package/bin/rip +16 -4
- package/docs/RIP-LANG.md +0 -42
- package/docs/RIP-TYPES.md +47 -52
- package/docs/demo.html +2 -2
- package/docs/dist/rip.js +2294 -1544
- package/docs/dist/rip.min.js +202 -192
- package/docs/dist/rip.min.js.br +0 -0
- package/package.json +1 -1
- package/rip-loader.js +2 -2
- package/src/AGENTS.md +76 -11
- package/src/browser.js +5 -5
- package/src/compiler.js +961 -639
- package/src/components.js +274 -109
- package/src/error.js +250 -0
- package/src/grammar/grammar.rip +2 -12
- package/src/lexer.js +15 -11
- package/src/parser.js +220 -223
- package/src/repl.js +3 -2
- package/src/sourcemap-utils.js +39 -6
- package/src/typecheck.js +312 -80
- package/src/types.js +229 -54
- package/src/ui.rip +4 -0
package/src/compiler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Rip Compiler — S-expression → JavaScript
|
|
2
2
|
//
|
|
3
|
-
// Architecture: Lexer (tokenize) → Parser (parse) →
|
|
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,
|
|
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
|
-
'&&', '||', '??', '
|
|
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
|
|
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': '
|
|
141
|
+
'program': 'emitProgram',
|
|
141
142
|
|
|
142
143
|
// Logical (flatten chains)
|
|
143
|
-
'&&': '
|
|
144
|
-
'||': '
|
|
144
|
+
'&&': 'emitLogicalAnd',
|
|
145
|
+
'||': 'emitLogicalOr',
|
|
145
146
|
|
|
146
147
|
// Binary operators (shared)
|
|
147
|
-
'+': '
|
|
148
|
-
'/': '
|
|
149
|
-
'==': '
|
|
150
|
-
'!==': '
|
|
151
|
-
'<=': '
|
|
152
|
-
'
|
|
153
|
-
'^': '
|
|
154
|
-
'>>>': '
|
|
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
|
-
'%%': '
|
|
158
|
-
'%%=': '
|
|
159
|
-
'//': '
|
|
160
|
-
'//=': '
|
|
161
|
-
'..': '
|
|
158
|
+
'%%': 'emitModulo',
|
|
159
|
+
'%%=': 'emitModuloAssign',
|
|
160
|
+
'//': 'emitFloorDiv',
|
|
161
|
+
'//=': 'emitFloorDivAssign',
|
|
162
|
+
'..': 'emitRange',
|
|
162
163
|
|
|
163
164
|
// Assignment (shared)
|
|
164
|
-
'=': '
|
|
165
|
-
'+=': '
|
|
166
|
-
'/=': '
|
|
167
|
-
'&&=': '
|
|
168
|
-
'?=': '
|
|
169
|
-
'^=': '
|
|
170
|
-
'>>>=': '
|
|
171
|
-
|
|
172
|
-
'...': '
|
|
173
|
-
'!': '
|
|
174
|
-
'~': '
|
|
175
|
-
'++': '
|
|
176
|
-
'--': '
|
|
177
|
-
'=~': '
|
|
178
|
-
'instanceof': '
|
|
179
|
-
'in': '
|
|
180
|
-
'of': '
|
|
181
|
-
'typeof': '
|
|
182
|
-
'delete': '
|
|
183
|
-
'new': '
|
|
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': '
|
|
187
|
-
'object': '
|
|
188
|
-
'map-literal': '
|
|
189
|
-
'block': '
|
|
187
|
+
'array': 'emitArray',
|
|
188
|
+
'object': 'emitObject',
|
|
189
|
+
'map-literal': 'emitMap',
|
|
190
|
+
'block': 'emitBlock',
|
|
190
191
|
|
|
191
192
|
// Property access
|
|
192
|
-
'.': '
|
|
193
|
-
'?.': '
|
|
194
|
-
'[]': '
|
|
195
|
-
'optindex': '
|
|
196
|
-
'optcall': '
|
|
197
|
-
'regex-index': '
|
|
193
|
+
'.': 'emitPropertyAccess',
|
|
194
|
+
'?.': 'emitOptionalProperty',
|
|
195
|
+
'[]': 'emitIndexAccess',
|
|
196
|
+
'optindex': 'emitOptIndex',
|
|
197
|
+
'optcall': 'emitOptCall',
|
|
198
|
+
'regex-index': 'emitRegexIndex',
|
|
198
199
|
|
|
199
200
|
// Functions
|
|
200
|
-
'def': '
|
|
201
|
-
'->': '
|
|
202
|
-
'=>': '
|
|
203
|
-
'return': '
|
|
201
|
+
'def': 'emitDef',
|
|
202
|
+
'->': 'emitThinArrow',
|
|
203
|
+
'=>': 'emitFatArrow',
|
|
204
|
+
'return': 'emitReturn',
|
|
204
205
|
|
|
205
206
|
// Reactive
|
|
206
|
-
'state': '
|
|
207
|
-
'computed': '
|
|
208
|
-
'readonly': '
|
|
209
|
-
'effect': '
|
|
207
|
+
'state': 'emitState',
|
|
208
|
+
'computed': 'emitComputed',
|
|
209
|
+
'readonly': 'emitReadonly',
|
|
210
|
+
'effect': 'emitEffect',
|
|
210
211
|
|
|
211
212
|
// Control flow — simple
|
|
212
|
-
'break': '
|
|
213
|
-
'continue': '
|
|
214
|
-
'?': '
|
|
215
|
-
'
|
|
216
|
-
'
|
|
217
|
-
'
|
|
218
|
-
'
|
|
219
|
-
'loop': '
|
|
220
|
-
'
|
|
221
|
-
'
|
|
222
|
-
'yield': '
|
|
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': '
|
|
227
|
-
'for-in': '
|
|
228
|
-
'for-of': '
|
|
229
|
-
'for-as': '
|
|
230
|
-
'while': '
|
|
231
|
-
'try': '
|
|
232
|
-
'throw': '
|
|
233
|
-
'control': '
|
|
234
|
-
'switch': '
|
|
235
|
-
'when': '
|
|
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': '
|
|
239
|
-
'object-comprehension': '
|
|
238
|
+
'comprehension': 'emitComprehension',
|
|
239
|
+
'object-comprehension': 'emitObjectComprehension',
|
|
240
240
|
|
|
241
241
|
// Classes
|
|
242
|
-
'class': '
|
|
243
|
-
'super': '
|
|
242
|
+
'class': 'emitClass',
|
|
243
|
+
'super': 'emitSuper',
|
|
244
244
|
|
|
245
245
|
// Components
|
|
246
|
-
'component': '
|
|
247
|
-
'render': '
|
|
248
|
-
'offer': '
|
|
249
|
-
'accept': '
|
|
246
|
+
'component': 'emitComponent',
|
|
247
|
+
'render': 'emitRender',
|
|
248
|
+
'offer': 'emitOffer',
|
|
249
|
+
'accept': 'emitAccept',
|
|
250
250
|
|
|
251
251
|
// Types
|
|
252
|
-
'enum': '
|
|
252
|
+
'enum': 'emitEnum',
|
|
253
253
|
|
|
254
254
|
// Modules
|
|
255
|
-
'import': '
|
|
256
|
-
'export': '
|
|
257
|
-
'export-default': '
|
|
258
|
-
'export-all': '
|
|
259
|
-
'export-from': '
|
|
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': '
|
|
263
|
-
'regex': '
|
|
264
|
-
'tagged-template': '
|
|
265
|
-
'str': '
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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))
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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}(${
|
|
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.
|
|
652
|
-
let needsParens =
|
|
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.
|
|
641
|
+
calleeCode = this.emit(head, 'value');
|
|
658
642
|
}
|
|
659
643
|
|
|
660
|
-
let
|
|
661
|
-
let callStr = `${calleeCode}(${args})`;
|
|
644
|
+
let callStr = `${calleeCode}(${this._emitArgs(rest)})`;
|
|
662
645
|
return needsAwait ? `await ${callStr}` : callStr;
|
|
663
646
|
}
|
|
664
647
|
|
|
665
|
-
|
|
648
|
+
this.error(`Unknown s-expression type: ${head}`, sexpr);
|
|
666
649
|
}
|
|
667
650
|
|
|
668
651
|
// ---------------------------------------------------------------------------
|
|
669
652
|
// Program
|
|
670
653
|
// ---------------------------------------------------------------------------
|
|
671
654
|
|
|
672
|
-
|
|
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.
|
|
697
|
-
else if (isLastComp) generated = this.
|
|
698
|
-
else generated = this.
|
|
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.
|
|
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 (
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
807
|
+
emitBinaryOp(op, rest, context, sexpr) {
|
|
810
808
|
if ((op === '+' || op === '-') && rest.length === 1) {
|
|
811
|
-
return `(${op}${this.
|
|
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.
|
|
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.
|
|
827
|
-
let b = this.
|
|
828
|
-
let c = this.
|
|
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.
|
|
832
|
+
return `(${this.emit(left, 'value')} ${op} ${this.emit(right, 'value')})`;
|
|
839
833
|
}
|
|
840
834
|
|
|
841
|
-
|
|
835
|
+
emitModulo(head, rest) {
|
|
842
836
|
let [left, right] = rest;
|
|
843
837
|
this.helpers.add('modulo');
|
|
844
|
-
return `modulo(${this.
|
|
838
|
+
return `modulo(${this.emit(left, 'value')}, ${this.emit(right, 'value')})`;
|
|
845
839
|
}
|
|
846
840
|
|
|
847
|
-
|
|
841
|
+
emitModuloAssign(head, rest) {
|
|
848
842
|
let [target, value] = rest;
|
|
849
843
|
this.helpers.add('modulo');
|
|
850
|
-
let t = this.
|
|
844
|
+
let t = this.emit(target, 'value'), v = this.emit(value, 'value');
|
|
851
845
|
return `${t} = modulo(${t}, ${v})`;
|
|
852
846
|
}
|
|
853
847
|
|
|
854
|
-
|
|
848
|
+
emitFloorDiv(head, rest) {
|
|
855
849
|
let [left, right] = rest;
|
|
856
|
-
return `Math.floor(${this.
|
|
850
|
+
return `Math.floor(${this.emit(left, 'value')} / ${this.emit(right, 'value')})`;
|
|
857
851
|
}
|
|
858
852
|
|
|
859
|
-
|
|
853
|
+
emitFloorDivAssign(head, rest) {
|
|
860
854
|
let [target, value] = rest;
|
|
861
|
-
let t = this.
|
|
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
|
-
|
|
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.
|
|
877
|
-
let targetCode = this.
|
|
878
|
-
let valueCode = this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
910
|
-
let exprCode = this.
|
|
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.
|
|
914
|
-
: (ctrlValue ? `throw ${this.
|
|
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.
|
|
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.
|
|
936
|
-
let afterPattern = afterRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.
|
|
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.
|
|
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.
|
|
975
|
-
let condCode = this.unwrapLogical(this.
|
|
976
|
-
let v = this.
|
|
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.
|
|
994
|
+
targetCode = this.emit(target, 'value');
|
|
989
995
|
}
|
|
990
996
|
|
|
991
997
|
const prevComponentName = this._componentName;
|
|
992
|
-
|
|
993
|
-
|
|
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
|
-
|
|
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.
|
|
1016
|
-
let needsParens =
|
|
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
|
-
|
|
1042
|
+
emitOptionalProperty(head, rest) {
|
|
1026
1043
|
let [obj, prop] = rest;
|
|
1027
|
-
return `${this.
|
|
1044
|
+
return `${this.emit(obj, 'value')}?.${prop}`;
|
|
1028
1045
|
}
|
|
1029
1046
|
|
|
1030
|
-
|
|
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.
|
|
1035
|
-
let idx = captureIndex !== null ? this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1074
|
-
let s = this.
|
|
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.
|
|
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.
|
|
1100
|
+
return `${this.emit(arr, 'value')}.at(-${n})`;
|
|
1084
1101
|
}
|
|
1085
1102
|
}
|
|
1086
|
-
return `${this.
|
|
1103
|
+
return `${this.emit(arr, 'value')}[${this.unwrap(this.emit(index, 'value'))}]`;
|
|
1087
1104
|
}
|
|
1088
1105
|
|
|
1089
|
-
|
|
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.
|
|
1112
|
+
return `${this.emit(arr, 'value')}?.at(-${n})`;
|
|
1096
1113
|
}
|
|
1097
1114
|
}
|
|
1098
|
-
return `${this.
|
|
1115
|
+
return `${this.emit(arr, 'value')}?.[${this.emit(index, 'value')}]`;
|
|
1099
1116
|
}
|
|
1100
1117
|
|
|
1101
|
-
|
|
1118
|
+
emitOptCall(head, rest) {
|
|
1102
1119
|
let [fn, ...args] = rest;
|
|
1103
|
-
return `${this.
|
|
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
|
-
|
|
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.
|
|
1115
|
-
let bodyCode = this.
|
|
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
|
-
|
|
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.
|
|
1127
|
-
let bodyCode = this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1181
|
+
let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
|
|
1165
1182
|
return `${prefix}${paramSyntax} => ${bodyCode}`;
|
|
1166
1183
|
}
|
|
1167
1184
|
|
|
1168
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1202
|
+
return `if (${this.emit(condition, 'value')}) return ${this.emit(['new', val], 'value')}`;
|
|
1186
1203
|
}
|
|
1187
|
-
return `return ${this.
|
|
1204
|
+
return `return ${this.emit(expr, 'value')}`;
|
|
1188
1205
|
}
|
|
1189
1206
|
|
|
1190
1207
|
// ---------------------------------------------------------------------------
|
|
1191
1208
|
// Reactive
|
|
1192
1209
|
// ---------------------------------------------------------------------------
|
|
1193
1210
|
|
|
1194
|
-
|
|
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.
|
|
1217
|
+
return `const ${varName} = __state(${this.emit(expr, 'value')})`;
|
|
1201
1218
|
}
|
|
1202
1219
|
|
|
1203
|
-
|
|
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.
|
|
1227
|
+
return `const ${varName} = __computed(() => ${this.emitFunctionBody(expr)})`;
|
|
1211
1228
|
}
|
|
1212
|
-
return `const ${varName} = __computed(() => ${this.
|
|
1229
|
+
return `const ${varName} = __computed(() => ${this.emit(expr, 'value')})`;
|
|
1213
1230
|
}
|
|
1214
1231
|
|
|
1215
|
-
|
|
1232
|
+
emitReadonly(head, rest) {
|
|
1216
1233
|
let [name, expr] = rest;
|
|
1217
|
-
return `const ${str(name) ?? name} = ${this.
|
|
1234
|
+
return `const ${str(name) ?? name} = ${this.emit(expr, 'value')}`;
|
|
1218
1235
|
}
|
|
1219
1236
|
|
|
1220
|
-
|
|
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.
|
|
1242
|
+
bodyCode = this.emitFunctionBody(body);
|
|
1226
1243
|
} else if ((this.is(body, '->') || this.is(body, '=>'))) {
|
|
1227
|
-
let fnCode = this.
|
|
1228
|
-
if (target) return `const ${str(target) ?? this.
|
|
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.
|
|
1248
|
+
bodyCode = `{ ${this.emit(body, 'value')}; }`;
|
|
1232
1249
|
}
|
|
1233
1250
|
let effectCode = `__effect(() => ${bodyCode})`;
|
|
1234
|
-
if (target) return `const ${str(target) ?? this.
|
|
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
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
1250
|
-
return `(${this.
|
|
1262
|
+
emitExistential(head, rest) {
|
|
1263
|
+
return `(${this.emit(rest[0], 'value')} != null)`;
|
|
1251
1264
|
}
|
|
1252
1265
|
|
|
1253
|
-
|
|
1254
|
-
return `(${this.
|
|
1266
|
+
emitPresence(head, rest) {
|
|
1267
|
+
return `(${this.emit(rest[0], 'value')} ? true : undefined)`;
|
|
1255
1268
|
}
|
|
1256
1269
|
|
|
1257
|
-
|
|
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.
|
|
1265
|
-
let thenVal = this.
|
|
1266
|
-
let elseVal = this.
|
|
1267
|
-
return `${target} = (${this.unwrap(this.
|
|
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.
|
|
1283
|
+
return `(${this.unwrap(this.emit(cond, 'value'))} ? ${this.emit(then_, 'value')} : ${this.emit(else_, 'value')})`;
|
|
1271
1284
|
}
|
|
1272
1285
|
|
|
1273
|
-
|
|
1286
|
+
emitPipe(head, rest) {
|
|
1274
1287
|
let [left, right] = rest;
|
|
1275
|
-
let leftCode = this.
|
|
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.
|
|
1282
|
-
let args = right.slice(1).map(a => this.
|
|
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.
|
|
1300
|
+
return `${this.emit(right, 'value')}(${leftCode})`;
|
|
1288
1301
|
}
|
|
1289
1302
|
|
|
1290
|
-
|
|
1291
|
-
return `while (true) ${this.
|
|
1303
|
+
emitLoop(head, rest) {
|
|
1304
|
+
return `while (true) ${this.emitLoopBody(rest[0])}`;
|
|
1292
1305
|
}
|
|
1293
1306
|
|
|
1294
|
-
|
|
1307
|
+
emitLoopN(head, rest) {
|
|
1295
1308
|
let [count, body] = rest;
|
|
1296
|
-
let n = this.
|
|
1297
|
-
return `for (let it = 0; it < ${n}; it++) ${this.
|
|
1309
|
+
let n = this.emit(count, 'value');
|
|
1310
|
+
return `for (let it = 0; it < ${n}; it++) ${this.emitLoopBody(body)}`;
|
|
1298
1311
|
}
|
|
1299
1312
|
|
|
1300
|
-
|
|
1313
|
+
emitAwait(head, rest) { return `await ${this.emit(rest[0], 'value')}`; }
|
|
1301
1314
|
|
|
1302
|
-
|
|
1303
|
-
return rest.length === 0 ? 'yield' : `yield ${this.
|
|
1315
|
+
emitYield(head, rest) {
|
|
1316
|
+
return rest.length === 0 ? 'yield' : `yield ${this.emit(rest[0], 'value')}`;
|
|
1304
1317
|
}
|
|
1305
1318
|
|
|
1306
|
-
|
|
1319
|
+
emitYieldFrom(head, rest) { return `yield* ${this.emit(rest[0], 'value')}`; }
|
|
1307
1320
|
|
|
1308
1321
|
// ---------------------------------------------------------------------------
|
|
1309
1322
|
// Conditionals
|
|
1310
1323
|
// ---------------------------------------------------------------------------
|
|
1311
1324
|
|
|
1312
|
-
|
|
1325
|
+
emitIf(head, rest, context, sexpr) {
|
|
1313
1326
|
let [condition, thenBranch, ...elseBranches] = rest;
|
|
1314
1327
|
return context === 'value'
|
|
1315
|
-
? this.
|
|
1316
|
-
: this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1348
|
+
? this.emitDestructuringPattern(itemVar) : itemVar;
|
|
1336
1349
|
|
|
1337
1350
|
// Stepped iteration
|
|
1338
1351
|
if (step && step !== null) {
|
|
1339
|
-
let iterCode = this.
|
|
1352
|
+
let iterCode = this.emit(iterable, 'value');
|
|
1340
1353
|
let idxName = indexVar || '_i';
|
|
1341
|
-
let stepCode = this.
|
|
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.
|
|
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.
|
|
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.
|
|
1373
|
-
: loopHeader + `{ ${this.
|
|
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.
|
|
1377
|
-
: loopHeader + `{ const ${itemVarPattern} = ${iterCode}[${idxName}]; ${this.
|
|
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.
|
|
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.
|
|
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.
|
|
1402
|
-
: `{ const ${itemVarPattern} = ${iterCode}[${indexVar}]; ${this.
|
|
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.
|
|
1429
|
+
let s = this.emit(start, 'value'), e = this.emit(end, 'value');
|
|
1417
1430
|
let cmp = isExcl ? '<' : '<=';
|
|
1418
|
-
let inc = step ? `${itemVarPattern} += ${this.
|
|
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.
|
|
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.
|
|
1427
|
-
code += guard ? this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1474
|
+
lines.push(`if (${this.emit(guard, 'value')}) {`);
|
|
1462
1475
|
this.indentLevel++;
|
|
1463
|
-
lines.push(...stmts.map(s => this.addSemicolon(s, this.
|
|
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.
|
|
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.
|
|
1476
|
-
inline += `${this.
|
|
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.
|
|
1493
|
+
code += guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body);
|
|
1481
1494
|
return code;
|
|
1482
1495
|
}
|
|
1483
1496
|
|
|
1484
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1544
|
+
code += guard ? this.emitLoopBodyWithGuard(body, guard) : this.emitLoopBody(body);
|
|
1532
1545
|
}
|
|
1533
1546
|
return code;
|
|
1534
1547
|
}
|
|
1535
1548
|
|
|
1536
|
-
|
|
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.
|
|
1539
|
-
return code + (guard ? this.
|
|
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
|
-
|
|
1555
|
+
emitRange(head, rest) {
|
|
1543
1556
|
if (head === '...') {
|
|
1544
|
-
if (rest.length === 1) return `...${this.
|
|
1557
|
+
if (rest.length === 1) return `...${this.emit(rest[0], 'value')}`;
|
|
1545
1558
|
let [s, e] = rest;
|
|
1546
|
-
let sc = this.
|
|
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.
|
|
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
|
-
|
|
1571
|
+
emitNot(head, rest) {
|
|
1559
1572
|
let [operand] = rest;
|
|
1560
|
-
if (typeof operand === 'string' || operand instanceof String) return `!${this.
|
|
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.
|
|
1576
|
+
if (highPrec.includes(operand[0])) return `!${this.emit(operand, 'value')}`;
|
|
1564
1577
|
}
|
|
1565
|
-
let code = this.
|
|
1578
|
+
let code = this.emit(operand, 'value');
|
|
1566
1579
|
return code.startsWith('(') ? `!${code}` : `(!${code})`;
|
|
1567
1580
|
}
|
|
1568
1581
|
|
|
1569
|
-
|
|
1582
|
+
emitBitwiseNot(head, rest) { return `(~${this.emit(rest[0], 'value')})`; }
|
|
1570
1583
|
|
|
1571
|
-
|
|
1584
|
+
emitIncDec(head, rest) {
|
|
1572
1585
|
let [operand, isPostfix] = rest;
|
|
1573
|
-
let code = this.
|
|
1586
|
+
let code = this.emit(operand, 'value');
|
|
1574
1587
|
return isPostfix ? `(${code}${head})` : `(${head}${code})`;
|
|
1575
1588
|
}
|
|
1576
1589
|
|
|
1577
|
-
|
|
1578
|
-
|
|
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
|
-
|
|
1593
|
+
emitInstanceof(head, rest, context, sexpr) {
|
|
1581
1594
|
let [expr, type] = rest;
|
|
1582
1595
|
let isNeg = meta(sexpr[0], 'invert');
|
|
1583
|
-
let result = `(${this.
|
|
1596
|
+
let result = `(${this.emit(expr, 'value')} instanceof ${this.emit(type, 'value')})`;
|
|
1584
1597
|
return isNeg ? `(!${result})` : result;
|
|
1585
1598
|
}
|
|
1586
1599
|
|
|
1587
|
-
|
|
1600
|
+
emitIn(head, rest, context, sexpr) {
|
|
1588
1601
|
let [key, container] = rest;
|
|
1589
|
-
let keyCode = this.
|
|
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.
|
|
1605
|
+
let result = `(${keyCode} in ${this.emit(container, 'value')})`;
|
|
1593
1606
|
return isNeg ? `(!${result})` : result;
|
|
1594
1607
|
}
|
|
1595
|
-
let c = this.
|
|
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
|
-
|
|
1613
|
+
emitOf(head, rest, context, sexpr) {
|
|
1601
1614
|
let [value, container] = rest;
|
|
1602
|
-
let v = this.
|
|
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
|
-
|
|
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.
|
|
1625
|
+
let r = this.emit(right, 'value');
|
|
1613
1626
|
let allowNL = r.includes('/m') ? ', true' : '';
|
|
1614
|
-
return `(_ = toMatchable(${this.
|
|
1627
|
+
return `(_ = toMatchable(${this.emit(left, 'value')}${allowNL}).match(${r}))`;
|
|
1615
1628
|
}
|
|
1616
1629
|
|
|
1617
|
-
|
|
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.
|
|
1635
|
+
return `(${this.emit(['new', target], 'value')}).${prop}`;
|
|
1623
1636
|
}
|
|
1624
|
-
return `new ${this.
|
|
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.
|
|
1641
|
+
return `new ${this.emit(ctor, 'value')}(${args.map(a => this.unwrap(this.emit(a, 'value'))).join(', ')})`;
|
|
1629
1642
|
}
|
|
1630
|
-
return `new ${this.
|
|
1643
|
+
return `new ${this.emit(call, 'value')}()`;
|
|
1631
1644
|
}
|
|
1632
1645
|
|
|
1633
1646
|
// ---------------------------------------------------------------------------
|
|
1634
1647
|
// Logical operators
|
|
1635
1648
|
// ---------------------------------------------------------------------------
|
|
1636
1649
|
|
|
1637
|
-
|
|
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.
|
|
1641
|
-
return `(${ops.map(o => this.
|
|
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
|
-
|
|
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.
|
|
1648
|
-
return `(${ops.map(o => this.
|
|
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
|
-
|
|
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.
|
|
1661
|
-
return this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1679
|
-
else if (this.is(key, 'str')) keyCode = `[${this.
|
|
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.
|
|
1695
|
+
keyCode = this.emit(key, 'value');
|
|
1683
1696
|
this.suppressReactiveUnwrap = false;
|
|
1684
1697
|
}
|
|
1685
|
-
let valCode = this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
!
|
|
1706
|
-
keyCode = isIdentifier ? `"${k}"` : this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1767
|
+
? this.emitBlockWithReturns(catchBlock) : this.emit(catchBlock, 'statement'));
|
|
1755
1768
|
} else if (rest.length === 2) {
|
|
1756
|
-
tryCode += ' finally ' + this.
|
|
1769
|
+
tryCode += ' finally ' + this.emit(rest[1], 'statement');
|
|
1757
1770
|
}
|
|
1758
1771
|
|
|
1759
|
-
if (rest.length === 3) tryCode += ' finally ' + this.
|
|
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
|
-
|
|
1765
|
-
|
|
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
|
-
|
|
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.
|
|
1784
|
-
let throwCode = `throw ${this.
|
|
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.
|
|
1802
|
+
let throwStmt = `throw ${this.emit(expr, 'value')}`;
|
|
1789
1803
|
return context === 'value' ? `(() => { ${throwStmt}; })()` : throwStmt;
|
|
1790
1804
|
}
|
|
1791
1805
|
|
|
1792
|
-
|
|
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.
|
|
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.
|
|
1800
|
-
: (ctrlValue ? `throw ${this.
|
|
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
|
-
|
|
1831
|
+
emitSwitch(head, rest, context) {
|
|
1818
1832
|
let [disc, whens, defaultCase] = rest;
|
|
1819
|
-
if (disc === null) return this.
|
|
1833
|
+
if (disc === null) return this.emitSwitchAsIfChain(whens, defaultCase, context);
|
|
1820
1834
|
|
|
1821
|
-
let switchBody = `switch (${this.
|
|
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.
|
|
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.
|
|
1844
|
+
else cv = this.emit(tv, 'value');
|
|
1831
1845
|
switchBody += this.indent() + `case ${cv}:\n`;
|
|
1832
1846
|
}
|
|
1833
1847
|
this.indentLevel++;
|
|
1834
|
-
switchBody += this.
|
|
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.
|
|
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
|
-
|
|
1848
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1905
|
-
let oc = this.
|
|
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.
|
|
1915
|
-
return { header: `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.
|
|
1929
|
+
? this.emitDestructuringPattern(fv) : fv;
|
|
1930
|
+
return { header: `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.emit(iterable, 'value')})` };
|
|
1916
1931
|
}
|
|
1917
1932
|
|
|
1918
|
-
|
|
1933
|
+
emitComprehension(head, rest, context) {
|
|
1919
1934
|
let [expr, iterators, guards] = rest;
|
|
1920
|
-
if (context === 'statement') return this.
|
|
1921
|
-
if (this.comprehensionTarget) return this.
|
|
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
|
-
|
|
1924
|
-
let
|
|
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.
|
|
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.
|
|
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.
|
|
1985
|
+
code += this.indent() + this.emit(s, 'statement') + ';\n';
|
|
1970
1986
|
} else {
|
|
1971
|
-
code += this.indent() + `result.push(${this.
|
|
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.
|
|
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.
|
|
1994
|
+
code += this.indent() + this.emit(expr, 'statement') + ';\n';
|
|
1979
1995
|
} else {
|
|
1980
|
-
code += this.indent() + `result.push(${this.
|
|
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
|
-
|
|
2009
|
+
emitObjectComprehension(head, rest, context) {
|
|
1994
2010
|
let [keyExpr, valueExpr, iterators, guards] = rest;
|
|
1995
|
-
|
|
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.
|
|
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.
|
|
2010
|
-
code += this.indent() + `result[${this.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
2102
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
2074
|
+
code += this.indent() + `static ${stmt[1][2]} = ${this.emit(stmt[2], 'value')};\n`;
|
|
2134
2075
|
} else {
|
|
2135
|
-
code += this.indent() + this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
2165
|
+
emitImport(head, rest, context, sexpr) {
|
|
2162
2166
|
if (rest.length === 1) {
|
|
2163
|
-
let importExpr = `import(${this.
|
|
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.
|
|
2187
|
+
return `import ${this.emit(specifier, 'value')} from ${fixedSource}`;
|
|
2184
2188
|
}
|
|
2185
2189
|
|
|
2186
|
-
|
|
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
|
-
|
|
2192
|
-
|
|
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.
|
|
2206
|
+
return this.emit(decl, 'statement');
|
|
2198
2207
|
}
|
|
2199
2208
|
if (this.is(decl, '=')) {
|
|
2200
2209
|
const prev = this._componentName;
|
|
2201
|
-
|
|
2202
|
-
|
|
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.
|
|
2221
|
+
return `export ${this.emit(decl, 'statement')}`;
|
|
2208
2222
|
}
|
|
2209
2223
|
|
|
2210
|
-
|
|
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.
|
|
2214
|
-
return this.
|
|
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.
|
|
2231
|
+
return `const ${expr[1]} = ${this.emit(expr[2], 'value')};\nexport default ${expr[1]}`;
|
|
2218
2232
|
}
|
|
2219
|
-
return `export default ${this.
|
|
2233
|
+
return `export default ${this.emit(expr, 'statement')}`;
|
|
2220
2234
|
}
|
|
2221
2235
|
|
|
2222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2243
|
-
return `(${this.
|
|
2256
|
+
emitDoIIFE(head, rest) {
|
|
2257
|
+
return `(${this.emit(rest[0], 'statement')})()`;
|
|
2244
2258
|
}
|
|
2245
2259
|
|
|
2246
|
-
|
|
2247
|
-
return rest.length === 0 ? head : this.
|
|
2260
|
+
emitRegex(head, rest) {
|
|
2261
|
+
return rest.length === 0 ? head : this.emit(rest[0], 'value');
|
|
2248
2262
|
}
|
|
2249
2263
|
|
|
2250
|
-
|
|
2264
|
+
emitTaggedTemplate(head, rest) {
|
|
2251
2265
|
let [tag, s] = rest;
|
|
2252
|
-
let tagCode = this.
|
|
2253
|
-
let content = this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
2471
|
-
else code += this.indent() + this.
|
|
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.
|
|
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.
|
|
2501
|
-
else if (Array.isArray(body) && (noRetStmts.includes(body[0]) || loopStmts.includes(body[0]))) result = `{ ${this.
|
|
2502
|
-
else if (sideEffectOnly) result = `{ ${this.
|
|
2503
|
-
else result = `{ return ${this.
|
|
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
|
-
|
|
2509
|
-
return this.
|
|
2558
|
+
emitFunctionBody(body, params = [], sideEffectOnly = false) {
|
|
2559
|
+
return this.emitBodyWithReturns(body, params, {sideEffectOnly, hasExpansionParams: this.expansionAfterParams?.length > 0});
|
|
2510
2560
|
}
|
|
2511
2561
|
|
|
2512
|
-
|
|
2513
|
-
return this.
|
|
2562
|
+
emitMethodBody(body, autoAssignments = [], isConstructor = false, params = []) {
|
|
2563
|
+
return this.emitBodyWithReturns(body, params, {autoAssignments, isConstructor});
|
|
2514
2564
|
}
|
|
2515
2565
|
|
|
2516
|
-
|
|
2517
|
-
if (!Array.isArray(block) || block[0] !== 'block') return this.
|
|
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.
|
|
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
|
-
|
|
2534
|
-
if (!Array.isArray(body)) return `{ ${this.
|
|
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.
|
|
2590
|
+
return this.indent() + this.emitComprehensionAsLoop(expr, iters, guards);
|
|
2541
2591
|
}
|
|
2542
|
-
return this.indent() + this.addSemicolon(s, this.
|
|
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.
|
|
2596
|
+
return `{ ${this.emit(body, 'statement')}; }`;
|
|
2547
2597
|
}
|
|
2548
2598
|
|
|
2549
|
-
|
|
2550
|
-
let guardCond = this.unwrap(this.
|
|
2551
|
-
if (!Array.isArray(body)) return `{ if (${guardCond}) ${this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2648
|
+
emitComprehensionAsLoop(expr, iterators, guards) {
|
|
2599
2649
|
let code = '';
|
|
2600
|
-
let guardCond = guards?.length ? guards.map(g => this.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2708
|
+
emitIfElseWithEarlyReturns(ifStmt) {
|
|
2659
2709
|
let [head, condition, thenBranch, ...elseBranches] = ifStmt;
|
|
2660
2710
|
let code = '';
|
|
2661
|
-
let condCode = this.
|
|
2711
|
+
let condCode = this.emit(condition, 'value');
|
|
2662
2712
|
code += this.indent() + `if (${condCode}) {\n`;
|
|
2663
|
-
code += this.withIndent(() => this.
|
|
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.
|
|
2670
|
-
code += this.withIndent(() => this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
2691
|
-
else code += this.indent() + this.
|
|
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
|
-
|
|
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 =
|
|
2702
|
-
code += `if (${this.
|
|
2703
|
-
code += this.
|
|
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.
|
|
2709
|
-
code += this.
|
|
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.
|
|
2715
|
-
code += this.
|
|
2765
|
+
code += `if (${this.emit(nnc, 'value')}) `;
|
|
2766
|
+
code += this.emitBlockWithReturns(nnt);
|
|
2716
2767
|
elseBranches.push(...nne);
|
|
2717
2768
|
} else {
|
|
2718
|
-
code += this.
|
|
2769
|
+
code += this.emitBlockWithReturns(nb);
|
|
2719
2770
|
}
|
|
2720
2771
|
}
|
|
2721
2772
|
} else {
|
|
2722
|
-
code += this.
|
|
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.
|
|
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
|
-
|
|
2735
|
-
let code = `if (${this.unwrap(this.
|
|
2736
|
-
code += this.
|
|
2737
|
-
for (let branch of elseBranches) code += ` else ` + this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
2751
|
-
else code += this.indent() + this.
|
|
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.
|
|
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.
|
|
2814
|
+
for (let s of stmts) code += this.indent() + this.emit(s, 'statement') + ';\n';
|
|
2764
2815
|
} else {
|
|
2765
|
-
code += this.indent() + this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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 (
|
|
3723
|
+
} catch (err) {
|
|
3414
3724
|
if (/\?\s*\([^)]*\?[^)]*:[^)]*\)\s*:/.test(source) || /\?\s+\w+\s+\?\s+/.test(source)) {
|
|
3415
|
-
throw new
|
|
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
|
|
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
|
|
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(
|
|
3793
|
+
installComponentSupport(CodeEmitter, Lexer);
|
|
3473
3794
|
|
|
3474
3795
|
// =============================================================================
|
|
3475
3796
|
// Type Support (enum generator)
|
|
3476
3797
|
// =============================================================================
|
|
3477
3798
|
|
|
3478
|
-
|
|
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
|
|
3493
|
-
return new
|
|
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
|
|
3836
|
+
return new CodeEmitter({}).getReactiveRuntime();
|
|
3516
3837
|
}
|
|
3517
3838
|
|
|
3518
3839
|
export function getComponentRuntime() {
|
|
3519
|
-
return new
|
|
3840
|
+
return new CodeEmitter({}).getComponentRuntime();
|
|
3520
3841
|
}
|
|
3521
3842
|
|
|
3522
3843
|
export { formatSExpr };
|
|
3844
|
+
export { RipError, toRipError, formatError, formatErrorHTML } from './error.js';
|