tova 0.3.4 → 0.3.6
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/bin/tova.js +438 -58
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +172 -32
- package/src/analyzer/client-analyzer.js +21 -5
- package/src/analyzer/scope.js +78 -3
- package/src/codegen/base-codegen.js +754 -45
- package/src/codegen/client-codegen.js +293 -36
- package/src/codegen/codegen.js +10 -15
- package/src/codegen/server-codegen.js +189 -40
- package/src/codegen/wasm-codegen.js +610 -0
- package/src/lexer/lexer.js +157 -109
- package/src/lexer/tokens.js +3 -0
- package/src/lsp/server.js +148 -12
- package/src/parser/ast.js +2 -1
- package/src/parser/client-parser.js +10 -3
- package/src/parser/parser.js +144 -150
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +307 -59
- package/src/runtime/ssr.js +101 -34
- package/src/stdlib/inline.js +333 -24
- package/src/stdlib/native-bridge.js +150 -0
- package/src/version.js +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// Base code generation utilities shared across all codegen targets
|
|
2
2
|
import { RESULT_OPTION, PROPAGATE, BUILTIN_NAMES, STDLIB_DEPS } from '../stdlib/inline.js';
|
|
3
3
|
import { PIPE_TARGET } from '../parser/ast.js';
|
|
4
|
+
import { compileWasmFunction, compileWasmModule, generateWasmGlue, generateMultiWasmGlue } from './wasm-codegen.js';
|
|
4
5
|
|
|
5
6
|
export class BaseCodegen {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.indent = 0;
|
|
8
9
|
this._counter = 0;
|
|
9
10
|
this._scopes = [new Set()]; // scope stack for tracking declared variables
|
|
11
|
+
this._visibleNames = new Set(); // flattened view of all declared names for O(1) lookup
|
|
12
|
+
this._nameRefCount = new Map(); // name -> count of scopes declaring it (for O(1) popScope)
|
|
10
13
|
this._needsContainsHelper = false; // track if __contains helper is needed
|
|
11
14
|
this._needsPropagateHelper = false; // track if __propagate helper is needed
|
|
12
15
|
this._usedBuiltins = new Set(); // track which stdlib builtins are actually used
|
|
@@ -16,10 +19,31 @@ export class BaseCodegen {
|
|
|
16
19
|
this._traitDecls = new Map(); // traitName -> { methods: [...] }
|
|
17
20
|
this._traitImpls = new Map(); // "TraitName:TypeName" -> ImplDeclaration node
|
|
18
21
|
// Source map tracking
|
|
22
|
+
this._sourceMapsEnabled = true; // can be disabled for REPL/check mode
|
|
23
|
+
this._propagateCache = new WeakMap(); // memoize _containsPropagate()
|
|
24
|
+
this._yieldCache = new WeakMap(); // memoize _containsYield()
|
|
19
25
|
this._sourceMappings = []; // {sourceLine, sourceCol, outputLine, outputCol, sourceFile?}
|
|
20
26
|
this._outputLineCount = 0;
|
|
21
27
|
this._sourceFile = null; // current source file for multi-file source maps
|
|
22
|
-
|
|
28
|
+
// @fast mode for TypedArray optimization
|
|
29
|
+
this._fastMode = false;
|
|
30
|
+
this._typedArrayParams = new Map(); // paramName -> 'Float64Array' | 'Int32Array' | 'Uint8Array'
|
|
31
|
+
this._typedArrayLocals = new Map(); // varName -> 'Float64Array' | 'Int32Array' | 'Uint8Array'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static TYPED_ARRAY_MAP = {
|
|
35
|
+
'Int': 'Int32Array',
|
|
36
|
+
'Float': 'Float64Array',
|
|
37
|
+
'Byte': 'Uint8Array',
|
|
38
|
+
'Int8': 'Int8Array',
|
|
39
|
+
'Int16': 'Int16Array',
|
|
40
|
+
'Int32': 'Int32Array',
|
|
41
|
+
'Uint8': 'Uint8Array',
|
|
42
|
+
'Uint16': 'Uint16Array',
|
|
43
|
+
'Uint32': 'Uint32Array',
|
|
44
|
+
'Float32': 'Float32Array',
|
|
45
|
+
'Float64': 'Float64Array',
|
|
46
|
+
};
|
|
23
47
|
|
|
24
48
|
_uid() {
|
|
25
49
|
return this._counter++;
|
|
@@ -43,11 +67,11 @@ export class BaseCodegen {
|
|
|
43
67
|
}
|
|
44
68
|
|
|
45
69
|
// Known void/side-effect-only calls that shouldn't be implicitly returned
|
|
70
|
+
static VOID_FNS = new Set(['print', 'assert', 'assert_eq', 'assert_ne']);
|
|
46
71
|
_isVoidCall(expr) {
|
|
47
72
|
if (expr.type !== 'CallExpression') return false;
|
|
48
73
|
if (expr.callee.type === 'Identifier') {
|
|
49
|
-
|
|
50
|
-
return voidFns.has(expr.callee.name);
|
|
74
|
+
return BaseCodegen.VOID_FNS.has(expr.callee.name);
|
|
51
75
|
}
|
|
52
76
|
return false;
|
|
53
77
|
}
|
|
@@ -59,18 +83,27 @@ export class BaseCodegen {
|
|
|
59
83
|
}
|
|
60
84
|
|
|
61
85
|
popScope() {
|
|
62
|
-
this._scopes.pop();
|
|
86
|
+
const removed = this._scopes.pop();
|
|
87
|
+
// O(n) cleanup using reference counts instead of O(n*m) scope search
|
|
88
|
+
for (const name of removed) {
|
|
89
|
+
const rc = this._nameRefCount.get(name) - 1;
|
|
90
|
+
if (rc <= 0) {
|
|
91
|
+
this._nameRefCount.delete(name);
|
|
92
|
+
this._visibleNames.delete(name);
|
|
93
|
+
} else {
|
|
94
|
+
this._nameRefCount.set(name, rc);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
63
97
|
}
|
|
64
98
|
|
|
65
99
|
declareVar(name) {
|
|
66
100
|
this._scopes[this._scopes.length - 1].add(name);
|
|
101
|
+
this._visibleNames.add(name);
|
|
102
|
+
this._nameRefCount.set(name, (this._nameRefCount.get(name) || 0) + 1);
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
isDeclared(name) {
|
|
70
|
-
|
|
71
|
-
if (this._scopes[i].has(name)) return true;
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
106
|
+
return this._visibleNames.has(name);
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
// ─── Helpers ────────────────────────────────────────────────
|
|
@@ -86,6 +119,7 @@ export class BaseCodegen {
|
|
|
86
119
|
|
|
87
120
|
// Source map: record a mapping from source location to output line
|
|
88
121
|
_addMapping(node, outputLine) {
|
|
122
|
+
if (!this._sourceMapsEnabled) return;
|
|
89
123
|
if (node && node.loc && node.loc.line) {
|
|
90
124
|
const mapping = {
|
|
91
125
|
sourceLine: node.loc.line - 1, // 0-based
|
|
@@ -155,20 +189,24 @@ export class BaseCodegen {
|
|
|
155
189
|
_containsPropagate(node) {
|
|
156
190
|
if (!node) return false;
|
|
157
191
|
if (node.type === 'PropagateExpression') return true;
|
|
158
|
-
// Stop at nested function/lambda boundaries — they get their own wrapper
|
|
159
192
|
if (node.type === 'FunctionDeclaration' || node.type === 'LambdaExpression') return false;
|
|
193
|
+
const cached = this._propagateCache.get(node);
|
|
194
|
+
if (cached !== undefined) return cached;
|
|
195
|
+
let result = false;
|
|
160
196
|
for (const key of Object.keys(node)) {
|
|
161
197
|
if (key === 'loc' || key === 'type') continue;
|
|
162
198
|
const val = node[key];
|
|
163
199
|
if (Array.isArray(val)) {
|
|
164
200
|
for (const item of val) {
|
|
165
|
-
if (item && typeof item === 'object' && this._containsPropagate(item))
|
|
201
|
+
if (item && typeof item === 'object' && this._containsPropagate(item)) { result = true; break; }
|
|
166
202
|
}
|
|
167
203
|
} else if (val && typeof val === 'object' && val.type) {
|
|
168
|
-
if (this._containsPropagate(val))
|
|
204
|
+
if (this._containsPropagate(val)) { result = true; break; }
|
|
169
205
|
}
|
|
206
|
+
if (result) break;
|
|
170
207
|
}
|
|
171
|
-
|
|
208
|
+
this._propagateCache.set(node, result);
|
|
209
|
+
return result;
|
|
172
210
|
}
|
|
173
211
|
|
|
174
212
|
getPropagateHelper() {
|
|
@@ -186,8 +224,8 @@ export class BaseCodegen {
|
|
|
186
224
|
generateStatement(node) {
|
|
187
225
|
if (!node) return '';
|
|
188
226
|
|
|
189
|
-
// Record source mapping before generating
|
|
190
|
-
this._addMapping(node, this._outputLineCount);
|
|
227
|
+
// Record source mapping before generating (skip when source maps disabled)
|
|
228
|
+
if (this._sourceMapsEnabled) this._addMapping(node, this._outputLineCount);
|
|
191
229
|
|
|
192
230
|
let result;
|
|
193
231
|
switch (node.type) {
|
|
@@ -230,8 +268,8 @@ export class BaseCodegen {
|
|
|
230
268
|
result = `${this.i()}${this.genExpression(node)};`;
|
|
231
269
|
}
|
|
232
270
|
|
|
233
|
-
// Track output line count using fast character scan
|
|
234
|
-
if (result) {
|
|
271
|
+
// Track output line count using fast character scan (skip when source maps disabled)
|
|
272
|
+
if (this._sourceMapsEnabled && result) {
|
|
235
273
|
this._outputLineCount += this._countLines(result) + 1; // +1 for the join newline
|
|
236
274
|
}
|
|
237
275
|
|
|
@@ -297,23 +335,59 @@ export class BaseCodegen {
|
|
|
297
335
|
const exportPrefix = node.isPublic ? 'export ' : '';
|
|
298
336
|
if (node.targets.length === 1 && node.values.length === 1) {
|
|
299
337
|
const target = node.targets[0];
|
|
338
|
+
const value = node.values[0];
|
|
300
339
|
// Member expression target: obj.x = expr, arr[i] = expr
|
|
301
340
|
if (typeof target === 'object' && target.type === 'MemberExpression') {
|
|
302
|
-
|
|
341
|
+
// IIFE elimination: match/if on RHS of member assignment
|
|
342
|
+
if (this._needsIIFE(value)) {
|
|
343
|
+
const memberExpr = this.genExpression(target);
|
|
344
|
+
const lines = [];
|
|
345
|
+
if (value.type === 'MatchExpression') {
|
|
346
|
+
lines.push(this._genMatchAssign(value, memberExpr));
|
|
347
|
+
} else {
|
|
348
|
+
lines.push(this._genIfAssign(value, memberExpr));
|
|
349
|
+
}
|
|
350
|
+
return lines.join('\n');
|
|
351
|
+
}
|
|
352
|
+
return `${this.i()}${this.genExpression(target)} = ${this.genExpression(value)};`;
|
|
303
353
|
}
|
|
304
354
|
if (target === '_') {
|
|
305
|
-
return `${this.i()}${this.genExpression(
|
|
355
|
+
return `${this.i()}${this.genExpression(value)};`;
|
|
306
356
|
}
|
|
307
357
|
if (this.isDeclared(target)) {
|
|
308
358
|
// Reassignment to an already-declared variable (must be mutable)
|
|
309
|
-
|
|
359
|
+
// IIFE elimination: match/if on RHS of reassignment (skip if binding conflicts)
|
|
360
|
+
if (this._needsIIFE(value) && !this._matchBindingsConflict(value, target)) {
|
|
361
|
+
if (value.type === 'MatchExpression') {
|
|
362
|
+
return this._genMatchAssign(value, target);
|
|
363
|
+
} else {
|
|
364
|
+
return this._genIfAssign(value, target);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return `${this.i()}${target} = ${this.genExpression(value)};`;
|
|
310
368
|
}
|
|
311
369
|
this.declareVar(target);
|
|
312
370
|
// Track top-level user definitions to avoid stdlib conflicts
|
|
313
371
|
if (this._scopes.length === 1 && BUILTIN_NAMES.has(target)) {
|
|
314
372
|
this._userDefinedNames.add(target);
|
|
315
373
|
}
|
|
316
|
-
|
|
374
|
+
// @fast mode: track typed array local variables for loop optimization
|
|
375
|
+
if (this._fastMode && this._typedArrayLocals) {
|
|
376
|
+
const taType = this._detectTypedArrayExpr(value);
|
|
377
|
+
if (taType) this._typedArrayLocals.set(target, taType);
|
|
378
|
+
}
|
|
379
|
+
// IIFE elimination: match/if on RHS of new const declaration (skip if binding conflicts)
|
|
380
|
+
if (this._needsIIFE(value) && !this._matchBindingsConflict(value, target)) {
|
|
381
|
+
const lines = [];
|
|
382
|
+
lines.push(`${this.i()}${exportPrefix}let ${target};`);
|
|
383
|
+
if (value.type === 'MatchExpression') {
|
|
384
|
+
lines.push(this._genMatchAssign(value, target));
|
|
385
|
+
} else {
|
|
386
|
+
lines.push(this._genIfAssign(value, target));
|
|
387
|
+
}
|
|
388
|
+
return lines.join('\n');
|
|
389
|
+
}
|
|
390
|
+
return `${this.i()}${exportPrefix}const ${target} = ${this.genExpression(value)};`;
|
|
317
391
|
}
|
|
318
392
|
|
|
319
393
|
// Multiple assignment: a, b = 1, 2 (uses destructuring for atomicity)
|
|
@@ -351,8 +425,21 @@ export class BaseCodegen {
|
|
|
351
425
|
genVarDeclaration(node) {
|
|
352
426
|
const exportPrefix = node.isPublic ? 'export ' : '';
|
|
353
427
|
if (node.targets.length === 1 && node.values.length === 1) {
|
|
354
|
-
|
|
355
|
-
|
|
428
|
+
const target = node.targets[0];
|
|
429
|
+
const value = node.values[0];
|
|
430
|
+
this.declareVar(target);
|
|
431
|
+
// IIFE elimination for var declarations too (skip if binding conflicts)
|
|
432
|
+
if (this._needsIIFE(value) && !this._matchBindingsConflict(value, target)) {
|
|
433
|
+
const lines = [];
|
|
434
|
+
lines.push(`${this.i()}${exportPrefix}let ${target};`);
|
|
435
|
+
if (value.type === 'MatchExpression') {
|
|
436
|
+
lines.push(this._genMatchAssign(value, target));
|
|
437
|
+
} else {
|
|
438
|
+
lines.push(this._genIfAssign(value, target));
|
|
439
|
+
}
|
|
440
|
+
return lines.join('\n');
|
|
441
|
+
}
|
|
442
|
+
return `${this.i()}${exportPrefix}let ${target} = ${this.genExpression(value)};`;
|
|
356
443
|
}
|
|
357
444
|
const lines = [];
|
|
358
445
|
for (let idx = 0; idx < node.targets.length; idx++) {
|
|
@@ -383,6 +470,30 @@ export class BaseCodegen {
|
|
|
383
470
|
}
|
|
384
471
|
|
|
385
472
|
genFunctionDeclaration(node) {
|
|
473
|
+
// Check for @wasm decorator — compile to WebAssembly
|
|
474
|
+
if (node.decorators && node.decorators.some(d => d.name === 'wasm')) {
|
|
475
|
+
return this.genWasmFunction(node);
|
|
476
|
+
}
|
|
477
|
+
// Check for @fast decorator — enable TypedArray optimizations
|
|
478
|
+
const isFast = node.decorators && node.decorators.some(d => d.name === 'fast');
|
|
479
|
+
const prevFastMode = this._fastMode;
|
|
480
|
+
const prevTypedParams = this._typedArrayParams;
|
|
481
|
+
const prevTypedLocals = this._typedArrayLocals;
|
|
482
|
+
if (isFast) {
|
|
483
|
+
this._fastMode = true;
|
|
484
|
+
this._typedArrayParams = new Map();
|
|
485
|
+
this._typedArrayLocals = new Map(); // track locally-created typed arrays
|
|
486
|
+
// Scan params for typed array annotations: param: [Int], param: [Float], param: [Byte]
|
|
487
|
+
for (const p of node.params) {
|
|
488
|
+
if (p.typeAnnotation && p.typeAnnotation.type === 'ArrayTypeAnnotation' && p.typeAnnotation.elementType) {
|
|
489
|
+
const elemName = p.typeAnnotation.elementType.name;
|
|
490
|
+
const typedArrayType = BaseCodegen.TYPED_ARRAY_MAP[elemName];
|
|
491
|
+
if (typedArrayType) {
|
|
492
|
+
this._typedArrayParams.set(p.name, typedArrayType);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
386
497
|
const params = this.genParams(node.params);
|
|
387
498
|
const hasPropagate = this._containsPropagate(node.body);
|
|
388
499
|
const isGenerator = this._containsYield(node.body);
|
|
@@ -399,20 +510,48 @@ export class BaseCodegen {
|
|
|
399
510
|
}
|
|
400
511
|
const body = this.genBlockBody(node.body);
|
|
401
512
|
this.popScope();
|
|
402
|
-
const
|
|
403
|
-
|
|
513
|
+
const lines = [];
|
|
514
|
+
lines.push(`${this.i()}${exportPrefix}${asyncPrefix}function${genStar} ${node.name}(${params}) {`);
|
|
515
|
+
// In @fast mode, convert typed array params at function entry
|
|
516
|
+
if (isFast && this._typedArrayParams.size > 0) {
|
|
517
|
+
for (const [pName, taType] of this._typedArrayParams) {
|
|
518
|
+
lines.push(`${this.i()} ${pName} = ${pName} instanceof ${taType} ? ${pName} : new ${taType}(${pName});`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
404
521
|
if (hasPropagate) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
522
|
+
lines.push(`${this.i()} try {`);
|
|
523
|
+
lines.push(body);
|
|
524
|
+
lines.push(`${this.i()} } catch (__e) {`);
|
|
525
|
+
lines.push(`${this.i()} if (__e && __e.__tova_propagate) return __e.value;`);
|
|
526
|
+
lines.push(`${this.i()} throw __e;`);
|
|
527
|
+
lines.push(`${this.i()} }`);
|
|
411
528
|
} else {
|
|
412
|
-
|
|
529
|
+
lines.push(body);
|
|
530
|
+
}
|
|
531
|
+
lines.push(`${this.i()}}`);
|
|
532
|
+
// Restore @fast state
|
|
533
|
+
if (isFast) {
|
|
534
|
+
this._fastMode = prevFastMode;
|
|
535
|
+
this._typedArrayParams = prevTypedParams;
|
|
536
|
+
this._typedArrayLocals = prevTypedLocals;
|
|
537
|
+
}
|
|
538
|
+
return lines.join('\n');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
genWasmFunction(node) {
|
|
542
|
+
try {
|
|
543
|
+
// Track as user-defined to suppress stdlib version
|
|
544
|
+
if (BUILTIN_NAMES.has(node.name)) this._userDefinedNames.add(node.name);
|
|
545
|
+
const wasmBytes = compileWasmFunction(node);
|
|
546
|
+
const glue = generateWasmGlue(node, wasmBytes);
|
|
547
|
+
const exportPrefix = node.isPublic ? 'export ' : '';
|
|
548
|
+
return `${this.i()}${exportPrefix}${glue}`;
|
|
549
|
+
} catch (e) {
|
|
550
|
+
// Fall back to JS if WASM compilation fails
|
|
551
|
+
console.error(`Warning: @wasm compilation failed for '${node.name}': ${e.message}. Falling back to JS.`);
|
|
552
|
+
node.decorators = node.decorators.filter(d => d.name !== 'wasm');
|
|
553
|
+
return this.genFunctionDeclaration(node);
|
|
413
554
|
}
|
|
414
|
-
p.push(`${this.i()}}`);
|
|
415
|
-
return p.join('\n');
|
|
416
555
|
}
|
|
417
556
|
|
|
418
557
|
genParams(params) {
|
|
@@ -490,12 +629,136 @@ export class BaseCodegen {
|
|
|
490
629
|
return p.join('');
|
|
491
630
|
}
|
|
492
631
|
|
|
632
|
+
// Check if a for-loop over a range can be emitted as a C-style for loop
|
|
633
|
+
_isRangeForOptimizable(node) {
|
|
634
|
+
const vars = Array.isArray(node.variable) ? node.variable : [node.variable];
|
|
635
|
+
if (vars.length !== 1 || typeof vars[0] !== 'string' || node.isAsync || node.elseBody) return false;
|
|
636
|
+
if (node.iterable.type === 'RangeExpression') return true;
|
|
637
|
+
// Optimize for i in range(n) / range(start, end) / range(start, end, step)
|
|
638
|
+
if (node.iterable.type === 'CallExpression' &&
|
|
639
|
+
node.iterable.callee.type === 'Identifier' &&
|
|
640
|
+
node.iterable.callee.name === 'range' &&
|
|
641
|
+
node.iterable.arguments.length >= 1 && node.iterable.arguments.length <= 3) return true;
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// @fast mode: detect if an expression produces a TypedArray
|
|
646
|
+
// Returns the TypedArray type string (e.g. 'Float64Array') or null
|
|
647
|
+
_detectTypedArrayExpr(value) {
|
|
648
|
+
if (!value) return null;
|
|
649
|
+
// Type.new(n) → new Type(n), where Type is a TypedArray
|
|
650
|
+
if (value.type === 'MethodCall' && value.methodName === 'new' &&
|
|
651
|
+
value.object && value.object.type === 'Identifier') {
|
|
652
|
+
const taType = BaseCodegen.TYPED_ARRAY_MAP[value.object.name];
|
|
653
|
+
if (taType) return taType;
|
|
654
|
+
// Direct TypedArray names: Float64Array.new(n)
|
|
655
|
+
if (Object.values(BaseCodegen.TYPED_ARRAY_MAP).includes(value.object.name)) return value.object.name;
|
|
656
|
+
}
|
|
657
|
+
// typed_add/typed_scale/typed_map/typed_sort return same type as input
|
|
658
|
+
if (value.type === 'CallExpression' && value.callee && value.callee.type === 'Identifier') {
|
|
659
|
+
const fname = value.callee.name;
|
|
660
|
+
if (['typed_add', 'typed_scale', 'typed_map', 'typed_sort'].includes(fname)) {
|
|
661
|
+
return 'Float64Array'; // conservative default
|
|
662
|
+
}
|
|
663
|
+
// typed_linspace returns Float64Array
|
|
664
|
+
if (fname === 'typed_linspace') return 'Float64Array';
|
|
665
|
+
}
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// @fast mode: check if a for-loop iterates over a known typed array
|
|
670
|
+
// Returns the TypedArray type or null
|
|
671
|
+
_getTypedArrayIterable(node) {
|
|
672
|
+
if (!this._fastMode) return null;
|
|
673
|
+
const iter = node.iterable;
|
|
674
|
+
if (iter.type !== 'Identifier') return null;
|
|
675
|
+
const name = iter.name;
|
|
676
|
+
return this._typedArrayParams.get(name) || (this._typedArrayLocals && this._typedArrayLocals.get(name)) || null;
|
|
677
|
+
}
|
|
678
|
+
|
|
493
679
|
genForStatement(node) {
|
|
494
680
|
const vars = Array.isArray(node.variable) ? node.variable : [node.variable];
|
|
495
|
-
const iterExpr = this.genExpression(node.iterable);
|
|
496
681
|
const labelPrefix = node.label ? `${node.label}: ` : '';
|
|
497
682
|
const awaitKeyword = node.isAsync ? ' await' : '';
|
|
498
683
|
|
|
684
|
+
// Optimization: for i in start..end => C-style for loop (avoids array allocation)
|
|
685
|
+
if (this._isRangeForOptimizable(node)) {
|
|
686
|
+
const varName = vars[0];
|
|
687
|
+
let start, end, step, cmpOp;
|
|
688
|
+
|
|
689
|
+
if (node.iterable.type === 'RangeExpression') {
|
|
690
|
+
start = this.genExpression(node.iterable.start);
|
|
691
|
+
end = this.genExpression(node.iterable.end);
|
|
692
|
+
cmpOp = node.iterable.inclusive ? '<=' : '<';
|
|
693
|
+
step = null;
|
|
694
|
+
} else {
|
|
695
|
+
// range(n) / range(start, end) / range(start, end, step)
|
|
696
|
+
const args = node.iterable.arguments;
|
|
697
|
+
if (args.length === 1) {
|
|
698
|
+
start = '0';
|
|
699
|
+
end = this.genExpression(args[0]);
|
|
700
|
+
cmpOp = '<';
|
|
701
|
+
} else if (args.length === 2) {
|
|
702
|
+
start = this.genExpression(args[0]);
|
|
703
|
+
end = this.genExpression(args[1]);
|
|
704
|
+
cmpOp = '<';
|
|
705
|
+
} else {
|
|
706
|
+
start = this.genExpression(args[0]);
|
|
707
|
+
end = this.genExpression(args[1]);
|
|
708
|
+
step = this.genExpression(args[2]);
|
|
709
|
+
cmpOp = '<';
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
this.pushScope();
|
|
714
|
+
this.declareVar(varName);
|
|
715
|
+
const p = [];
|
|
716
|
+
if (step) {
|
|
717
|
+
// With explicit step: need to handle positive and negative step
|
|
718
|
+
const stepVar = `__step_${this._uid()}`;
|
|
719
|
+
p.push(`${this.i()}${labelPrefix}{ const ${stepVar} = ${step};\n`);
|
|
720
|
+
p.push(`${this.i()}for (let ${varName} = ${start}; ${stepVar} > 0 ? ${varName} < ${end} : ${varName} > ${end}; ${varName} += ${stepVar}) {\n`);
|
|
721
|
+
} else {
|
|
722
|
+
p.push(`${this.i()}${labelPrefix}for (let ${varName} = ${start}; ${varName} ${cmpOp} ${end}; ${varName}++) {\n`);
|
|
723
|
+
}
|
|
724
|
+
this.indent++;
|
|
725
|
+
if (node.guard) {
|
|
726
|
+
p.push(`${this.i()}if (!(${this.genExpression(node.guard)})) continue;\n`);
|
|
727
|
+
}
|
|
728
|
+
p.push(this.genBlockStatements(node.body));
|
|
729
|
+
this.indent--;
|
|
730
|
+
p.push(`\n${this.i()}}`);
|
|
731
|
+
if (step) p.push(`\n${this.i()}}`);
|
|
732
|
+
this.popScope();
|
|
733
|
+
return p.join('');
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// @fast mode optimization: for val in typedArray => index-based loop (avoids iterator overhead)
|
|
737
|
+
if (vars.length === 1 && !node.isAsync && !node.elseBody) {
|
|
738
|
+
const taType = this._getTypedArrayIterable(node);
|
|
739
|
+
if (taType) {
|
|
740
|
+
const varName = vars[0];
|
|
741
|
+
const arrName = node.iterable.name;
|
|
742
|
+
const idxVar = `__i_${this._uid()}`;
|
|
743
|
+
this.pushScope();
|
|
744
|
+
this.declareVar(varName);
|
|
745
|
+
const p = [];
|
|
746
|
+
p.push(`${this.i()}${labelPrefix}for (let ${idxVar} = 0; ${idxVar} < ${arrName}.length; ${idxVar}++) {\n`);
|
|
747
|
+
this.indent++;
|
|
748
|
+
p.push(`${this.i()}const ${varName} = ${arrName}[${idxVar}];\n`);
|
|
749
|
+
if (node.guard) {
|
|
750
|
+
p.push(`${this.i()}if (!(${this.genExpression(node.guard)})) continue;\n`);
|
|
751
|
+
}
|
|
752
|
+
p.push(this.genBlockStatements(node.body));
|
|
753
|
+
this.indent--;
|
|
754
|
+
p.push(`\n${this.i()}}`);
|
|
755
|
+
this.popScope();
|
|
756
|
+
return p.join('');
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const iterExpr = this.genExpression(node.iterable);
|
|
761
|
+
|
|
499
762
|
if (node.elseBody) {
|
|
500
763
|
// for-else: run else if iterable was empty
|
|
501
764
|
const tempVar = `__iter_${this._uid()}`;
|
|
@@ -684,7 +947,15 @@ export class BaseCodegen {
|
|
|
684
947
|
// Implicit return: last expression in function body
|
|
685
948
|
// Skip implicit return for known void/side-effect-only calls (print, assert, etc.)
|
|
686
949
|
if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
|
|
687
|
-
|
|
950
|
+
// IIFE elimination: match/if as last expression in function body → direct returns
|
|
951
|
+
const expr = stmt.expression;
|
|
952
|
+
if (expr.type === 'MatchExpression' && !this._isSimpleMatch(expr)) {
|
|
953
|
+
lines.push(this._genMatchReturn(expr));
|
|
954
|
+
} else if (expr.type === 'IfExpression' && this._needsIIFE(expr)) {
|
|
955
|
+
lines.push(this._genIfReturn(expr));
|
|
956
|
+
} else {
|
|
957
|
+
lines.push(`${this.i()}return ${this.genExpression(stmt.expression)};`);
|
|
958
|
+
}
|
|
688
959
|
} else if (isLast && stmt.type === 'IfStatement' && stmt.elseBody) {
|
|
689
960
|
lines.push(this._genIfStatementWithReturns(stmt));
|
|
690
961
|
} else if (isLast && stmt.type === 'MatchExpression') {
|
|
@@ -759,10 +1030,61 @@ export class BaseCodegen {
|
|
|
759
1030
|
return lines.join('\n');
|
|
760
1031
|
}
|
|
761
1032
|
|
|
1033
|
+
_genBlockBodyAssign(block, targetVar) {
|
|
1034
|
+
// Like _genBlockBodyReturns but emits `targetVar = expr` instead of `return expr`
|
|
1035
|
+
if (!block) return '';
|
|
1036
|
+
const stmts = block.type === 'BlockStatement' ? block.body : [block];
|
|
1037
|
+
this.indent++;
|
|
1038
|
+
const lines = [];
|
|
1039
|
+
for (let idx = 0; idx < stmts.length; idx++) {
|
|
1040
|
+
const stmt = stmts[idx];
|
|
1041
|
+
const isLast = idx === stmts.length - 1;
|
|
1042
|
+
if (isLast && stmt.type === 'ExpressionStatement') {
|
|
1043
|
+
lines.push(`${this.i()}${targetVar} = ${this.genExpression(stmt.expression)};`);
|
|
1044
|
+
} else if (isLast && stmt.type === 'IfStatement' && stmt.elseBody) {
|
|
1045
|
+
lines.push(this._genIfStatementWithAssigns(stmt, targetVar));
|
|
1046
|
+
} else if (isLast && stmt.type === 'MatchExpression') {
|
|
1047
|
+
// Nested match inside block — generate as assignment too
|
|
1048
|
+
lines.push(this._genMatchAssign(stmt, targetVar));
|
|
1049
|
+
} else {
|
|
1050
|
+
lines.push(this.generateStatement(stmt));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
this.indent--;
|
|
1054
|
+
return lines.join('\n');
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
_genIfStatementWithAssigns(node, targetVar) {
|
|
1058
|
+
const p = [];
|
|
1059
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
1060
|
+
p.push(this._genBlockBodyAssign(node.consequent, targetVar));
|
|
1061
|
+
p.push(`\n${this.i()}}`);
|
|
1062
|
+
|
|
1063
|
+
for (const alt of node.alternates) {
|
|
1064
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
1065
|
+
p.push(this._genBlockBodyAssign(alt.body, targetVar));
|
|
1066
|
+
p.push(`\n${this.i()}}`);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (node.elseBody) {
|
|
1070
|
+
p.push(` else {\n`);
|
|
1071
|
+
p.push(this._genBlockBodyAssign(node.elseBody, targetVar));
|
|
1072
|
+
p.push(`\n${this.i()}}`);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
return p.join('');
|
|
1076
|
+
}
|
|
1077
|
+
|
|
762
1078
|
genBlockStatements(block) {
|
|
763
1079
|
if (!block) return '';
|
|
764
1080
|
const stmts = block.type === 'BlockStatement' ? block.body : [block];
|
|
765
|
-
|
|
1081
|
+
const lines = [];
|
|
1082
|
+
for (const s of stmts) {
|
|
1083
|
+
lines.push(this.generateStatement(s));
|
|
1084
|
+
// Dead code elimination: stop after unconditional return/break/continue
|
|
1085
|
+
if (s.type === 'ReturnStatement' || s.type === 'BreakStatement' || s.type === 'ContinueStatement') break;
|
|
1086
|
+
}
|
|
1087
|
+
return lines.join('\n');
|
|
766
1088
|
}
|
|
767
1089
|
|
|
768
1090
|
// ─── Expressions ──────────────────────────────────────────
|
|
@@ -778,9 +1100,32 @@ export class BaseCodegen {
|
|
|
778
1100
|
}
|
|
779
1101
|
|
|
780
1102
|
genBinaryExpression(node) {
|
|
1103
|
+
const op = node.operator;
|
|
1104
|
+
|
|
1105
|
+
// Constant folding: arithmetic on two number literals
|
|
1106
|
+
if (node.left.type === 'NumberLiteral' && node.right.type === 'NumberLiteral') {
|
|
1107
|
+
const l = node.left.value, r = node.right.value;
|
|
1108
|
+
let folded = null;
|
|
1109
|
+
switch (op) {
|
|
1110
|
+
case '+': folded = l + r; break;
|
|
1111
|
+
case '-': folded = l - r; break;
|
|
1112
|
+
case '*': folded = l * r; break;
|
|
1113
|
+
case '/': if (r !== 0) folded = l / r; break;
|
|
1114
|
+
case '%': if (r !== 0) folded = l % r; break;
|
|
1115
|
+
case '**': folded = l ** r; break;
|
|
1116
|
+
}
|
|
1117
|
+
if (folded !== null && Number.isFinite(folded)) {
|
|
1118
|
+
return folded < 0 ? `(${folded})` : String(folded);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Constant folding: string concatenation with ++
|
|
1123
|
+
if (op === '++' && node.left.type === 'StringLiteral' && node.right.type === 'StringLiteral') {
|
|
1124
|
+
return JSON.stringify(node.left.value + node.right.value);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
781
1127
|
const left = this.genExpression(node.left);
|
|
782
1128
|
const right = this.genExpression(node.right);
|
|
783
|
-
const op = node.operator;
|
|
784
1129
|
|
|
785
1130
|
// String multiply: "ha" * 3 => "ha".repeat(3), also x * 3 when x is string
|
|
786
1131
|
if (op === '*' &&
|
|
@@ -1533,12 +1878,83 @@ export class BaseCodegen {
|
|
|
1533
1878
|
return `(${result})`;
|
|
1534
1879
|
}
|
|
1535
1880
|
|
|
1881
|
+
// Check if all arms are literal patterns (string/number/boolean) or wildcard, with no guards
|
|
1882
|
+
_isLiteralMatch(node) {
|
|
1883
|
+
let hasWildcard = false;
|
|
1884
|
+
for (const arm of node.arms) {
|
|
1885
|
+
if (arm.guard) return false;
|
|
1886
|
+
const pt = arm.pattern.type;
|
|
1887
|
+
if (pt === 'LiteralPattern') continue;
|
|
1888
|
+
if (pt === 'WildcardPattern' || pt === 'BindingPattern') {
|
|
1889
|
+
hasWildcard = true;
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
return false;
|
|
1893
|
+
}
|
|
1894
|
+
return true;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
_genSwitchMatch(node) {
|
|
1898
|
+
const subject = this.genExpression(node.subject);
|
|
1899
|
+
const tempVar = '__match';
|
|
1900
|
+
const p = [];
|
|
1901
|
+
p.push(`((${tempVar}) => {\n`);
|
|
1902
|
+
this.indent++;
|
|
1903
|
+
p.push(`${this.i()}switch (${tempVar}) {\n`);
|
|
1904
|
+
this.indent++;
|
|
1905
|
+
|
|
1906
|
+
for (const arm of node.arms) {
|
|
1907
|
+
if (arm.pattern.type === 'WildcardPattern') {
|
|
1908
|
+
p.push(`${this.i()}default:\n`);
|
|
1909
|
+
this.indent++;
|
|
1910
|
+
if (arm.body.type === 'BlockStatement') {
|
|
1911
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1912
|
+
} else {
|
|
1913
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1914
|
+
}
|
|
1915
|
+
this.indent--;
|
|
1916
|
+
} else if (arm.pattern.type === 'BindingPattern') {
|
|
1917
|
+
p.push(`${this.i()}default: {\n`);
|
|
1918
|
+
this.indent++;
|
|
1919
|
+
p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
|
|
1920
|
+
if (arm.body.type === 'BlockStatement') {
|
|
1921
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1922
|
+
} else {
|
|
1923
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1924
|
+
}
|
|
1925
|
+
this.indent--;
|
|
1926
|
+
p.push(`${this.i()}}\n`);
|
|
1927
|
+
} else {
|
|
1928
|
+
// LiteralPattern
|
|
1929
|
+
p.push(`${this.i()}case ${JSON.stringify(arm.pattern.value)}:\n`);
|
|
1930
|
+
this.indent++;
|
|
1931
|
+
if (arm.body.type === 'BlockStatement') {
|
|
1932
|
+
p.push(this.genBlockBody(arm.body) + '\n');
|
|
1933
|
+
} else {
|
|
1934
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
1935
|
+
}
|
|
1936
|
+
this.indent--;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
this.indent--;
|
|
1941
|
+
p.push(`${this.i()}}\n`);
|
|
1942
|
+
this.indent--;
|
|
1943
|
+
p.push(`${this.i()}})(${subject})`);
|
|
1944
|
+
return p.join('');
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1536
1947
|
genMatchExpression(node) {
|
|
1537
1948
|
// Optimization: simple matches emit ternary chain instead of IIFE
|
|
1538
1949
|
if (this._isSimpleMatch(node)) {
|
|
1539
1950
|
return this._genSimpleMatch(node);
|
|
1540
1951
|
}
|
|
1541
1952
|
|
|
1953
|
+
// Optimization: literal-only patterns emit switch for V8 jump tables
|
|
1954
|
+
if (this._isLiteralMatch(node)) {
|
|
1955
|
+
return this._genSwitchMatch(node);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1542
1958
|
// Generate as IIFE with if-else chain
|
|
1543
1959
|
const subject = this.genExpression(node.subject);
|
|
1544
1960
|
const tempVar = '__match';
|
|
@@ -1635,6 +2051,280 @@ export class BaseCodegen {
|
|
|
1635
2051
|
return p.join('');
|
|
1636
2052
|
}
|
|
1637
2053
|
|
|
2054
|
+
// ─── IIFE-free match/if codegen (assign to variable instead of return) ───
|
|
2055
|
+
|
|
2056
|
+
_genMatchAssign(node, targetVar) {
|
|
2057
|
+
// Block-scoped match that assigns to targetVar instead of wrapping in IIFE
|
|
2058
|
+
// Handles switch optimization and general if-else chain
|
|
2059
|
+
|
|
2060
|
+
// Simple ternary matches don't need this optimization (already no IIFE)
|
|
2061
|
+
// Literal-only switch matches benefit from assignment form
|
|
2062
|
+
|
|
2063
|
+
if (this._isLiteralMatch(node)) {
|
|
2064
|
+
return this._genSwitchMatchAssign(node, targetVar);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
const subject = this.genExpression(node.subject);
|
|
2068
|
+
const tempVar = `__match`;
|
|
2069
|
+
const p = [];
|
|
2070
|
+
p.push(`${this.i()}{\n`);
|
|
2071
|
+
this.indent++;
|
|
2072
|
+
p.push(`${this.i()}const ${tempVar} = ${subject};\n`);
|
|
2073
|
+
|
|
2074
|
+
for (let idx = 0; idx < node.arms.length; idx++) {
|
|
2075
|
+
const arm = node.arms[idx];
|
|
2076
|
+
const condition = this.genPatternCondition(arm.pattern, tempVar, arm.guard);
|
|
2077
|
+
|
|
2078
|
+
if (arm.pattern.type === 'WildcardPattern' || arm.pattern.type === 'BindingPattern') {
|
|
2079
|
+
if (idx === node.arms.length - 1 && !arm.guard) {
|
|
2080
|
+
// Default case — wrap in else if preceded by if branches
|
|
2081
|
+
if (idx > 0) {
|
|
2082
|
+
p.push(` else {\n`);
|
|
2083
|
+
this.indent++;
|
|
2084
|
+
}
|
|
2085
|
+
if (arm.pattern.type === 'BindingPattern') {
|
|
2086
|
+
p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
|
|
2087
|
+
}
|
|
2088
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2089
|
+
p.push(this._genBlockBodyAssign(arm.body, targetVar) + '\n');
|
|
2090
|
+
} else {
|
|
2091
|
+
p.push(`${this.i()}${targetVar} = ${this.genExpression(arm.body)};\n`);
|
|
2092
|
+
}
|
|
2093
|
+
if (idx > 0) {
|
|
2094
|
+
this.indent--;
|
|
2095
|
+
p.push(`${this.i()}}\n`);
|
|
2096
|
+
}
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (idx === 0) {
|
|
2102
|
+
p.push(`${this.i()}if (${condition}) {\n`);
|
|
2103
|
+
} else {
|
|
2104
|
+
p.push(` else if (${condition}) {\n`);
|
|
2105
|
+
}
|
|
2106
|
+
this.indent++;
|
|
2107
|
+
|
|
2108
|
+
// Bind variables from pattern
|
|
2109
|
+
p.push(this.genPatternBindings(arm.pattern, tempVar));
|
|
2110
|
+
|
|
2111
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2112
|
+
p.push(this._genBlockBodyAssign(arm.body, targetVar) + '\n');
|
|
2113
|
+
} else {
|
|
2114
|
+
p.push(`${this.i()}${targetVar} = ${this.genExpression(arm.body)};\n`);
|
|
2115
|
+
}
|
|
2116
|
+
this.indent--;
|
|
2117
|
+
p.push(`${this.i()}}`);
|
|
2118
|
+
}
|
|
2119
|
+
p.push('\n');
|
|
2120
|
+
|
|
2121
|
+
this.indent--;
|
|
2122
|
+
p.push(`${this.i()}}`);
|
|
2123
|
+
return p.join('');
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
_genSwitchMatchAssign(node, targetVar) {
|
|
2127
|
+
// Switch-based match that assigns to targetVar instead of wrapping in IIFE
|
|
2128
|
+
const subject = this.genExpression(node.subject);
|
|
2129
|
+
const tempVar = '__match';
|
|
2130
|
+
const p = [];
|
|
2131
|
+
p.push(`${this.i()}{\n`);
|
|
2132
|
+
this.indent++;
|
|
2133
|
+
p.push(`${this.i()}const ${tempVar} = ${subject};\n`);
|
|
2134
|
+
p.push(`${this.i()}switch (${tempVar}) {\n`);
|
|
2135
|
+
this.indent++;
|
|
2136
|
+
|
|
2137
|
+
for (const arm of node.arms) {
|
|
2138
|
+
if (arm.pattern.type === 'WildcardPattern') {
|
|
2139
|
+
p.push(`${this.i()}default:\n`);
|
|
2140
|
+
this.indent++;
|
|
2141
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2142
|
+
p.push(this._genBlockBodyAssign(arm.body, targetVar) + '\n');
|
|
2143
|
+
} else {
|
|
2144
|
+
p.push(`${this.i()}${targetVar} = ${this.genExpression(arm.body)};\n`);
|
|
2145
|
+
}
|
|
2146
|
+
p.push(`${this.i()}break;\n`);
|
|
2147
|
+
this.indent--;
|
|
2148
|
+
} else if (arm.pattern.type === 'BindingPattern') {
|
|
2149
|
+
p.push(`${this.i()}default: {\n`);
|
|
2150
|
+
this.indent++;
|
|
2151
|
+
p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
|
|
2152
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2153
|
+
p.push(this._genBlockBodyAssign(arm.body, targetVar) + '\n');
|
|
2154
|
+
} else {
|
|
2155
|
+
p.push(`${this.i()}${targetVar} = ${this.genExpression(arm.body)};\n`);
|
|
2156
|
+
}
|
|
2157
|
+
p.push(`${this.i()}break;\n`);
|
|
2158
|
+
this.indent--;
|
|
2159
|
+
p.push(`${this.i()}}\n`);
|
|
2160
|
+
} else {
|
|
2161
|
+
// LiteralPattern
|
|
2162
|
+
p.push(`${this.i()}case ${JSON.stringify(arm.pattern.value)}:\n`);
|
|
2163
|
+
this.indent++;
|
|
2164
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2165
|
+
p.push(this._genBlockBodyAssign(arm.body, targetVar) + '\n');
|
|
2166
|
+
} else {
|
|
2167
|
+
p.push(`${this.i()}${targetVar} = ${this.genExpression(arm.body)};\n`);
|
|
2168
|
+
}
|
|
2169
|
+
p.push(`${this.i()}break;\n`);
|
|
2170
|
+
this.indent--;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
this.indent--;
|
|
2175
|
+
p.push(`${this.i()}}\n`);
|
|
2176
|
+
this.indent--;
|
|
2177
|
+
p.push(`${this.i()}}`);
|
|
2178
|
+
return p.join('');
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
_genIfAssign(node, targetVar) {
|
|
2182
|
+
// Block-scoped if expression that assigns to targetVar instead of wrapping in IIFE
|
|
2183
|
+
const p = [];
|
|
2184
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
2185
|
+
p.push(this._genBlockBodyAssign(node.consequent, targetVar));
|
|
2186
|
+
p.push(`\n${this.i()}}`);
|
|
2187
|
+
|
|
2188
|
+
for (const alt of node.alternates) {
|
|
2189
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
2190
|
+
p.push(this._genBlockBodyAssign(alt.body, targetVar));
|
|
2191
|
+
p.push(`\n${this.i()}}`);
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
if (node.elseBody) {
|
|
2195
|
+
p.push(` else {\n`);
|
|
2196
|
+
p.push(this._genBlockBodyAssign(node.elseBody, targetVar));
|
|
2197
|
+
p.push(`\n${this.i()}}`);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
return p.join('');
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// IIFE-free match/if that emits direct returns (for function body last expression)
|
|
2204
|
+
_genMatchReturn(node) {
|
|
2205
|
+
const subject = this.genExpression(node.subject);
|
|
2206
|
+
const tempVar = `__match`;
|
|
2207
|
+
const p = [];
|
|
2208
|
+
p.push(`${this.i()}{\n`);
|
|
2209
|
+
this.indent++;
|
|
2210
|
+
p.push(`${this.i()}const ${tempVar} = ${subject};\n`);
|
|
2211
|
+
|
|
2212
|
+
for (let idx = 0; idx < node.arms.length; idx++) {
|
|
2213
|
+
const arm = node.arms[idx];
|
|
2214
|
+
const condition = this.genPatternCondition(arm.pattern, tempVar, arm.guard);
|
|
2215
|
+
|
|
2216
|
+
if (arm.pattern.type === 'WildcardPattern' || arm.pattern.type === 'BindingPattern') {
|
|
2217
|
+
if (idx === node.arms.length - 1 && !arm.guard) {
|
|
2218
|
+
if (idx > 0) {
|
|
2219
|
+
p.push(` else {\n`);
|
|
2220
|
+
this.indent++;
|
|
2221
|
+
}
|
|
2222
|
+
if (arm.pattern.type === 'BindingPattern') {
|
|
2223
|
+
p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
|
|
2224
|
+
}
|
|
2225
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2226
|
+
p.push(this._genBlockBodyReturns(arm.body) + '\n');
|
|
2227
|
+
} else {
|
|
2228
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
2229
|
+
}
|
|
2230
|
+
if (idx > 0) {
|
|
2231
|
+
this.indent--;
|
|
2232
|
+
p.push(`${this.i()}}\n`);
|
|
2233
|
+
}
|
|
2234
|
+
break;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
if (idx === 0) {
|
|
2239
|
+
p.push(`${this.i()}if (${condition}) {\n`);
|
|
2240
|
+
} else {
|
|
2241
|
+
p.push(` else if (${condition}) {\n`);
|
|
2242
|
+
}
|
|
2243
|
+
this.indent++;
|
|
2244
|
+
p.push(this.genPatternBindings(arm.pattern, tempVar));
|
|
2245
|
+
if (arm.body.type === 'BlockStatement') {
|
|
2246
|
+
p.push(this._genBlockBodyReturns(arm.body) + '\n');
|
|
2247
|
+
} else {
|
|
2248
|
+
p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
|
|
2249
|
+
}
|
|
2250
|
+
this.indent--;
|
|
2251
|
+
p.push(`${this.i()}}`);
|
|
2252
|
+
}
|
|
2253
|
+
p.push('\n');
|
|
2254
|
+
this.indent--;
|
|
2255
|
+
p.push(`${this.i()}}`);
|
|
2256
|
+
return p.join('');
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
_genIfReturn(node) {
|
|
2260
|
+
const p = [];
|
|
2261
|
+
p.push(`${this.i()}if (${this.genExpression(node.condition)}) {\n`);
|
|
2262
|
+
p.push(this._genBlockBodyReturns(node.consequent));
|
|
2263
|
+
p.push(`\n${this.i()}}`);
|
|
2264
|
+
|
|
2265
|
+
for (const alt of node.alternates) {
|
|
2266
|
+
p.push(` else if (${this.genExpression(alt.condition)}) {\n`);
|
|
2267
|
+
p.push(this._genBlockBodyReturns(alt.body));
|
|
2268
|
+
p.push(`\n${this.i()}}`);
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
if (node.elseBody) {
|
|
2272
|
+
p.push(` else {\n`);
|
|
2273
|
+
p.push(this._genBlockBodyReturns(node.elseBody));
|
|
2274
|
+
p.push(`\n${this.i()}}`);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
return p.join('');
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
// Check if a match/if expression would need IIFE (not simple ternary)
|
|
2281
|
+
_needsIIFE(node) {
|
|
2282
|
+
if (node.type === 'MatchExpression') {
|
|
2283
|
+
return !this._isSimpleMatch(node);
|
|
2284
|
+
}
|
|
2285
|
+
if (node.type === 'IfExpression') {
|
|
2286
|
+
const isSingleExpr = (block) =>
|
|
2287
|
+
block.type === 'BlockStatement' && block.body.length === 1 && block.body[0].type === 'ExpressionStatement';
|
|
2288
|
+
// Simple if/elif → ternary (no IIFE)
|
|
2289
|
+
if (node.alternates.length === 0 && isSingleExpr(node.consequent) && isSingleExpr(node.elseBody)) return false;
|
|
2290
|
+
if (node.alternates.length > 0 && node.elseBody &&
|
|
2291
|
+
isSingleExpr(node.consequent) && isSingleExpr(node.elseBody) &&
|
|
2292
|
+
node.alternates.every(alt => isSingleExpr(alt.body))) return false;
|
|
2293
|
+
// Otherwise needs IIFE (or our new assign path)
|
|
2294
|
+
return true;
|
|
2295
|
+
}
|
|
2296
|
+
return false;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// Check if a match has any pattern bindings that would conflict with the target variable
|
|
2300
|
+
_matchBindingsConflict(node, targetVar) {
|
|
2301
|
+
if (node.type !== 'MatchExpression') return false;
|
|
2302
|
+
for (const arm of node.arms) {
|
|
2303
|
+
if (this._patternBindsName(arm.pattern, targetVar)) return true;
|
|
2304
|
+
}
|
|
2305
|
+
return false;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
_patternBindsName(pattern, name) {
|
|
2309
|
+
switch (pattern.type) {
|
|
2310
|
+
case 'BindingPattern':
|
|
2311
|
+
return pattern.name === name;
|
|
2312
|
+
case 'VariantPattern':
|
|
2313
|
+
return pattern.fields.some(f => {
|
|
2314
|
+
if (typeof f === 'string') return f === name;
|
|
2315
|
+
if (f && f.type) return this._patternBindsName(f, name);
|
|
2316
|
+
return false;
|
|
2317
|
+
});
|
|
2318
|
+
case 'ArrayPattern':
|
|
2319
|
+
case 'TuplePattern':
|
|
2320
|
+
return pattern.elements.some(el => el && this._patternBindsName(el, name));
|
|
2321
|
+
case 'StringConcatPattern':
|
|
2322
|
+
return pattern.rest && pattern.rest.type === 'BindingPattern' && pattern.rest.name === name;
|
|
2323
|
+
default:
|
|
2324
|
+
return false;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
|
|
1638
2328
|
genPatternCondition(pattern, subject, guard) {
|
|
1639
2329
|
let cond;
|
|
1640
2330
|
|
|
@@ -1764,6 +2454,12 @@ export class BaseCodegen {
|
|
|
1764
2454
|
|
|
1765
2455
|
genArrayLiteral(node) {
|
|
1766
2456
|
const elements = node.elements.map(e => this.genExpression(e)).join(', ');
|
|
2457
|
+
// In @fast mode, detect all-numeric arrays and emit TypedArrays
|
|
2458
|
+
if (this._fastMode && node.elements.length > 0 && node.elements.every(e => e.type === 'NumberLiteral')) {
|
|
2459
|
+
const hasFloat = node.elements.some(e => String(e.value).includes('.'));
|
|
2460
|
+
const taType = hasFloat ? 'Float64Array' : 'Int32Array';
|
|
2461
|
+
return `new ${taType}([${elements}])`;
|
|
2462
|
+
}
|
|
1767
2463
|
return `[${elements}]`;
|
|
1768
2464
|
}
|
|
1769
2465
|
|
|
@@ -1791,7 +2487,8 @@ export class BaseCodegen {
|
|
|
1791
2487
|
if (expr === varName) {
|
|
1792
2488
|
return `${iter}.filter((${varName}) => ${cond})`;
|
|
1793
2489
|
}
|
|
1794
|
-
|
|
2490
|
+
// Single-pass loop avoids intermediate array from filter().map()
|
|
2491
|
+
return `${iter}.reduce((acc, ${varName}) => { if (${cond}) acc.push(${expr}); return acc; }, [])`;
|
|
1795
2492
|
}
|
|
1796
2493
|
return `${iter}.map((${varName}) => ${expr})`;
|
|
1797
2494
|
}
|
|
@@ -1815,10 +2512,12 @@ export class BaseCodegen {
|
|
|
1815
2512
|
genRangeExpression(node) {
|
|
1816
2513
|
const start = this.genExpression(node.start);
|
|
1817
2514
|
const end = this.genExpression(node.end);
|
|
2515
|
+
// Use stdlib range() — handles step and direction, avoids Array.from overhead
|
|
2516
|
+
this._trackBuiltin('range');
|
|
1818
2517
|
if (node.inclusive) {
|
|
1819
|
-
return `
|
|
2518
|
+
return `range(${start}, (${end}) + 1)`;
|
|
1820
2519
|
}
|
|
1821
|
-
return `
|
|
2520
|
+
return `range(${start}, ${end})`;
|
|
1822
2521
|
}
|
|
1823
2522
|
|
|
1824
2523
|
genSliceExpression(node) {
|
|
@@ -1885,14 +2584,19 @@ export class BaseCodegen {
|
|
|
1885
2584
|
for (const variant of node.variants) {
|
|
1886
2585
|
if (variant.type === 'TypeVariant') {
|
|
1887
2586
|
this.declareVar(variant.name);
|
|
1888
|
-
const
|
|
2587
|
+
const rawFieldNames = variant.fields.map(f => f.name);
|
|
2588
|
+
// Deduplicate field names: Add(Expr, Expr) → _0, _1 (prevents property collision)
|
|
2589
|
+
const nameCount = {};
|
|
2590
|
+
rawFieldNames.forEach(n => nameCount[n] = (nameCount[n] || 0) + 1);
|
|
2591
|
+
const hasDupes = Object.values(nameCount).some(c => c > 1);
|
|
2592
|
+
const fieldNames = hasDupes ? rawFieldNames.map((_, i) => `_${i}`) : rawFieldNames;
|
|
1889
2593
|
this._variantFields[variant.name] = fieldNames;
|
|
1890
2594
|
if (variant.fields.length === 0) {
|
|
1891
2595
|
lines.push(`${this.i()}${exportPrefix}const ${variant.name} = Object.freeze({ __tag: "${variant.name}" });`);
|
|
1892
2596
|
} else {
|
|
1893
2597
|
const params = fieldNames.join(', ');
|
|
1894
2598
|
const obj = fieldNames.map(f => `${f}`).join(', ');
|
|
1895
|
-
lines.push(`${this.i()}${exportPrefix}function ${variant.name}(${params}) { return
|
|
2599
|
+
lines.push(`${this.i()}${exportPrefix}function ${variant.name}(${params}) { return { __tag: "${variant.name}", ${obj} }; }`);
|
|
1896
2600
|
}
|
|
1897
2601
|
}
|
|
1898
2602
|
}
|
|
@@ -2087,17 +2791,22 @@ export class BaseCodegen {
|
|
|
2087
2791
|
if (!node) return false;
|
|
2088
2792
|
if (node.type === 'YieldExpression') return true;
|
|
2089
2793
|
if (node.type === 'FunctionDeclaration' || node.type === 'LambdaExpression') return false;
|
|
2794
|
+
const cached = this._yieldCache.get(node);
|
|
2795
|
+
if (cached !== undefined) return cached;
|
|
2796
|
+
let result = false;
|
|
2090
2797
|
for (const key of Object.keys(node)) {
|
|
2091
2798
|
if (key === 'loc' || key === 'type') continue;
|
|
2092
2799
|
const val = node[key];
|
|
2093
2800
|
if (Array.isArray(val)) {
|
|
2094
2801
|
for (const item of val) {
|
|
2095
|
-
if (item && typeof item === 'object' && this._containsYield(item))
|
|
2802
|
+
if (item && typeof item === 'object' && this._containsYield(item)) { result = true; break; }
|
|
2096
2803
|
}
|
|
2097
2804
|
} else if (val && typeof val === 'object' && val.type) {
|
|
2098
|
-
if (this._containsYield(val))
|
|
2805
|
+
if (this._containsYield(val)) { result = true; break; }
|
|
2099
2806
|
}
|
|
2807
|
+
if (result) break;
|
|
2100
2808
|
}
|
|
2101
|
-
|
|
2809
|
+
this._yieldCache.set(node, result);
|
|
2810
|
+
return result;
|
|
2102
2811
|
}
|
|
2103
2812
|
}
|