@xnoxs/flux-lang 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/codegen.js DELETED
@@ -1,832 +0,0 @@
1
- 'use strict';
2
-
3
- // ── Format spec extractor for string interpolation {n:.2f} ───────────────────
4
- function extractFormatSpec(raw) {
5
- const ci = raw.lastIndexOf(':');
6
- if (ci < 1) return null;
7
- const spec = raw.slice(ci + 1).trim();
8
- // Must contain at least one format-type char: . < > ^ , or letter format
9
- if (spec.length > 0 && /[.<>^,dbeEfFgGoOxXs%bcn]/.test(spec) &&
10
- /^([.<>^0\-+ #,]*[0-9]*\.?[0-9]*[dbeEfFgGoOxXs%bcn]?[,]?)$/.test(spec)) {
11
- return { expr: raw.slice(0, ci).trim(), fmt: spec };
12
- }
13
- return null;
14
- }
15
-
16
- // ── Runtime _fmt helper (injected when format specs are detected) ─────────────
17
- const FMT_HELPER = `
18
- function _fmt(v, s) {
19
- if (s === ',') return (+v).toLocaleString();
20
- if (s === '%') return ((+v)*100).toFixed(0)+'%';
21
- var m = s.match(/^([0-9,]*)?\\.([0-9]+)([fdgGeEb%x])$/);
22
- if (m) {
23
- var d=+m[2], t=m[3], comma=s[0]===',';
24
- if (t==='f'||t==='d') return comma ? (+v).toLocaleString(void 0,{minimumFractionDigits:d,maximumFractionDigits:d}) : (+v).toFixed(d);
25
- if (t==='e'||t==='E') return (+v).toExponential(d);
26
- if (t==='%') return ((+v)*100).toFixed(d)+'%';
27
- if (t==='b') return Math.round(+v).toString(2);
28
- if (t==='x') return Math.round(+v).toString(16);
29
- }
30
- var m2 = s.match(/^([0-9]*)d$/); if (m2) return Math.round(+v).toString();
31
- return String(v);
32
- }`;
33
-
34
- class CodeGenError extends Error {
35
- constructor(msg, node) {
36
- super(`${msg}${node ? ` [${node.type}]` : ''}`);
37
- this.name = 'CodeGenError';
38
- }
39
- }
40
-
41
- // ── Class registry (pre-pass) ─────────────────────────────────────────────────
42
- function buildClassRegistry(ast) {
43
- const reg = {};
44
- for (const node of ast.body) {
45
- const n = node.type === 'ExportDecl' ? node.decl : node;
46
- if (n.type === 'ClassDecl')
47
- reg[n.name] = { fields: n.fields, superClass: n.superClass };
48
- }
49
- return reg;
50
- }
51
-
52
- function getAllFields(name, reg, visited = new Set()) {
53
- if (!name || !reg[name] || visited.has(name)) return [];
54
- visited.add(name);
55
- const parent = getAllFields(reg[name].superClass, reg, visited);
56
- return [...parent, ...reg[name].fields];
57
- }
58
-
59
- // ── Code Generator ────────────────────────────────────────────────────────────
60
- class CodeGenerator {
61
- constructor(opts = {}) {
62
- this.ind = opts.indent || ' ';
63
- this.level = 0;
64
- this.lines = [];
65
- this.clsReg = {};
66
- this.smBuilder = opts.smBuilder || null;
67
- this._needsFmt = false;
68
- this._loopDepth = 0; // track for/while nesting — match arms must not `return` inside loops
69
- }
70
-
71
- i() { return this.ind.repeat(this.level); }
72
- emit(s) { this.lines.push(this.i() + s); }
73
- emitRaw(s) { this.lines.push(s); }
74
- blank() { this.lines.push(''); }
75
-
76
- in() { this.level++; }
77
- out() { this.level = Math.max(0, this.level - 1); }
78
-
79
- // Record a source-map mapping for the next line to be emitted.
80
- // srcLoc: token-style { line (1-based), col (1-based) }
81
- _map(srcLoc) {
82
- if (!this.smBuilder || !srcLoc) return;
83
- const genLine = this.lines.length; // 0-based line about to be pushed
84
- const genCol = this.ind.repeat(this.level).length;
85
- const srcLine = (srcLoc.line || 1) - 1; // convert to 0-based
86
- const srcCol = (srcLoc.col || 1) - 1;
87
- this.smBuilder.addMapping(genLine, genCol, srcLine, srcCol);
88
- }
89
-
90
- generate(ast) {
91
- this.clsReg = buildClassRegistry(ast);
92
- this.lines = [];
93
- this.level = 0;
94
- this._needsFmt = false;
95
-
96
- this.emit('// Generated by Flux Transpiler v3.2.0');
97
- this.emit('"use strict";');
98
- this.blank();
99
-
100
- for (const node of ast.body) this.genStmt(node);
101
-
102
- // Inject _fmt helper if any format specs were used
103
- if (this._needsFmt) {
104
- this.lines.splice(2, 0, FMT_HELPER);
105
- }
106
-
107
- return { code: this.lines.join('\n'), smBuilder: this.smBuilder };
108
- }
109
-
110
- // ── Statements ─────────────────────────────────────────────────
111
- genStmt(node) {
112
- // Record source location for every statement (function, var, if, etc.)
113
- this._map(node.loc);
114
- switch (node.type) {
115
- case 'VarDecl': return this.genVar(node);
116
- case 'DestructureDecl':return this.genDestructure(node);
117
- case 'FnDecl': return this.genFn(node);
118
- case 'ClassDecl': return this.genClass(node);
119
- case 'IfStmt': return this.genIf(node);
120
- case 'ForInStmt': return this.genFor(node);
121
- case 'WhileStmt': return this.genWhile(node);
122
- case 'MatchStmt': return this.genMatch(node);
123
- case 'ReturnStmt': return this.genReturn(node);
124
- case 'TryCatchStmt': return this.genTryCatch(node);
125
- case 'ThrowStmt': return this.genThrow(node);
126
- case 'DoWhileStmt': return this.genDoWhile(node);
127
- case 'BreakStmt': return this.emit(node.label ? `break ${node.label};` : 'break;');
128
- case 'ContinueStmt': return this.emit(node.label ? `continue ${node.label};` : 'continue;');
129
- case 'LabeledStmt': return this.genLabeled(node);
130
- case 'ImportDecl': return this.genImport(node);
131
- case 'ExportDecl': return this.genExport(node);
132
- case 'TypeDecl': return this.genTypeDecl(node);
133
- case 'InterfaceDecl': return this.genInterfaceDecl(node);
134
- case 'EnumDecl': return this.genEnumDecl(node);
135
- case 'ExprStmt': return this.emit(this.genExpr(node.expr) + ';');
136
- default:
137
- throw new CodeGenError(`Unknown statement: ${node.type}`, node);
138
- }
139
- }
140
-
141
- // ── var / val ───────────────────────────────────────────────────
142
- genVar(node) {
143
- const kw = node.kind === 'val' ? 'const' : 'let';
144
- const init = node.init ? ` = ${this.genExpr(node.init)}` : '';
145
- this.emit(`${kw} ${node.name}${init};`);
146
- }
147
-
148
- // ── destructuring ───────────────────────────────────────────────
149
- genDestructure(node) {
150
- const kw = node.kind === 'val' ? 'const' : 'let';
151
- const init = this.genExpr(node.init);
152
-
153
- if (node.patternType === 'object') {
154
- const props = node.pattern.map(p => {
155
- if (p.rest) return `...${p.key}`;
156
- let s = p.alias !== p.key ? `${p.key}: ${p.alias}` : p.key;
157
- if (p.defaultVal) s += ` = ${this.genExpr(p.defaultVal)}`;
158
- return s;
159
- }).join(', ');
160
- this.emit(`${kw} { ${props} } = ${init};`);
161
- } else {
162
- const items = node.pattern.map(p => {
163
- if (!p) return '';
164
- if (p.rest) return `...${p.name}`;
165
- let s = p.name;
166
- if (p.defaultVal) s += ` = ${this.genExpr(p.defaultVal)}`;
167
- return s;
168
- }).join(', ');
169
- this.emit(`${kw} [${items}] = ${init};`);
170
- }
171
- }
172
-
173
- // ── fn ──────────────────────────────────────────────────────────
174
- genFn(node, prefix = '') {
175
- const asyncKw = node.async ? 'async ' : '';
176
- const name = node.name || '';
177
- const params = node.params.map(p =>
178
- p.rest ? `...${p.name}` :
179
- p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
180
- p.name
181
- ).join(', ');
182
-
183
- if (node.inline) {
184
- this.emit(`${prefix}${asyncKw}function ${name}(${params}) { return ${this.genExpr(node.body)}; }`);
185
- } else {
186
- this.emit(`${prefix}${asyncKw}function ${name}(${params}) {`);
187
- this.in();
188
- for (const s of node.body) this.genStmt(s);
189
- this.out();
190
- this.emit('}');
191
- }
192
- // Apply decorators: @log fn foo() → foo = log(foo);
193
- if (node.name && node.decorators && node.decorators.length > 0) {
194
- for (let i = node.decorators.length - 1; i >= 0; i--) {
195
- const dec = node.decorators[i];
196
- const decArgs = dec.args.length > 0
197
- ? `(${dec.args.map(a => this.genExpr(a)).join(', ')})(${node.name})`
198
- : `(${node.name})`;
199
- this.emit(`${node.name} = ${dec.name}${decArgs};`);
200
- }
201
- }
202
- }
203
-
204
- // ── class ──────────────────────────────────────────────────────
205
- genClass(node) {
206
- const ext = node.superClass ? ` extends ${node.superClass}` : '';
207
- this.emit(`class ${node.name}${ext} {`);
208
- this.in();
209
-
210
- // Determine which fields are private (use JS #field syntax)
211
- const isPrivate = f => f.modifiers && f.modifiers.has('private');
212
- const fieldName = f => isPrivate(f) ? `#${f.name}` : f.name;
213
- const fieldRef = f => isPrivate(f) ? `this.#${f.name}` : `this.${f.name}`;
214
-
215
- // Declare private fields at the top of the class body
216
- const privateFields = node.fields.filter(isPrivate);
217
- for (const f of privateFields) this.emit(`#${f.name};`);
218
- if (privateFields.length > 0) this.blank();
219
-
220
- // Constructor
221
- // Fields split into:
222
- // paramFields — no default init (become constructor params)
223
- // initFields — have a default init expression (assigned in body, not params)
224
- const allFields = getAllFields(node.name, this.clsReg);
225
- const paramFields = allFields.filter(f => f.init == null && !isPrivate(f));
226
- const initFields = allFields.filter(f => f.init != null);
227
- const ownInitFields = node.fields.filter(f => f.init != null);
228
-
229
- const needsCtor = paramFields.length > 0 || initFields.length > 0 || privateFields.length > 0;
230
- if (needsCtor) {
231
- const params = paramFields.map(f => f.name).join(', ');
232
- this.emit(`constructor(${params}) {`);
233
- this.in();
234
-
235
- // super(...parentParamFields) if extending
236
- if (node.superClass && this.clsReg[node.superClass]) {
237
- const parentFields = getAllFields(node.superClass, this.clsReg);
238
- const parentParams = parentFields.filter(f => f.init == null);
239
- if (parentParams.length > 0)
240
- this.emit(`super(${parentParams.map(f => f.name).join(', ')});`);
241
- }
242
-
243
- // Assign own non-private param-fields
244
- for (const f of node.fields.filter(f => f.init == null && !isPrivate(f)))
245
- this.emit(`${fieldRef(f)} = ${f.name};`);
246
-
247
- // Assign own init-fields (including private ones with defaults)
248
- for (const f of ownInitFields)
249
- this.emit(`${fieldRef(f)} = ${this.genExpr(f.init)};`);
250
-
251
- this.out();
252
- this.emit('}');
253
- this.blank();
254
- }
255
-
256
- // Methods
257
- for (const m of node.methods) {
258
- const asyncKw = m.async ? 'async ' : '';
259
- const staticKw = (m.modifiers && m.modifiers.has('static')) ? 'static ' : '';
260
- const getsetKw = m.getset ? `${m.getset} ` : '';
261
- const params = m.params.map(p =>
262
- p.rest ? `...${p.name}` :
263
- p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
264
- p.name
265
- ).join(', ');
266
- if (m.inline) {
267
- this.emit(`${staticKw}${asyncKw}${getsetKw}${m.name}(${params}) { return ${this.genExpr(m.body)}; }`);
268
- } else {
269
- this.emit(`${staticKw}${asyncKw}${getsetKw}${m.name}(${params}) {`);
270
- this.in();
271
- for (const s of m.body) this.genStmt(s);
272
- this.out();
273
- this.emit('}');
274
- }
275
- this.blank();
276
- }
277
-
278
- this.out();
279
- this.emit('}');
280
-
281
- // Apply class decorators: @injectable class Foo → Foo = injectable(Foo);
282
- if (node.decorators && node.decorators.length > 0) {
283
- for (let i = node.decorators.length - 1; i >= 0; i--) {
284
- const dec = node.decorators[i];
285
- const decArgs = dec.args.length > 0
286
- ? `(${dec.args.map(a => this.genExpr(a)).join(', ')})(${node.name})`
287
- : `(${node.name})`;
288
- this.emit(`${node.name} = ${dec.name}${decArgs};`);
289
- }
290
- }
291
-
292
- this.blank();
293
- }
294
-
295
- // ── if / else if / else ─────────────────────────────────────────
296
- genIf(node) {
297
- this.emit(`if (${this.genExpr(node.cond)}) {`);
298
- this.in(); node.then.forEach(s => this.genStmt(s)); this.out();
299
- this.emit('}');
300
-
301
- for (const ei of node.elseifs) {
302
- this.emitRaw(this.i() + `else if (${this.genExpr(ei.cond)}) {`);
303
- this.in(); ei.body.forEach(s => this.genStmt(s)); this.out();
304
- this.emit('}');
305
- }
306
-
307
- if (node.else_) {
308
- this.emitRaw(this.i() + 'else {');
309
- this.in(); node.else_.forEach(s => this.genStmt(s)); this.out();
310
- this.emit('}');
311
- }
312
- }
313
-
314
- // ── for x in iter / for i in 0..10 / for [a,b] in arr ──────────
315
- genFor(node) {
316
- const iter = node.iter;
317
- const label = node.label ? `${node.label}: ` : '';
318
-
319
- if (iter.type === 'RangeExpr') {
320
- const s = this.genExpr(iter.start);
321
- const e = this.genExpr(iter.end);
322
- this.emit(`${label}for (let ${node.var} = ${s}; ${node.var} < ${e}; ${node.var}++) {`);
323
- } else if (node.isAwait) {
324
- this.emit(`${label}for await (const ${node.var} of ${this.genExpr(iter)}) {`);
325
- } else {
326
- this.emit(`${label}for (const ${node.var} of ${this.genExpr(iter)}) {`);
327
- }
328
- this._loopDepth++;
329
- this.in();
330
- // Destructuring in loop var: for [a, b] in arr
331
- if (node.varPattern) {
332
- const p = node.varPattern;
333
- if (p.type === 'array') {
334
- const names = p.names.map(n => n.rest ? `...${n.name}` : n.name).join(', ');
335
- this.emit(`const [${names}] = ${node.var};`);
336
- }
337
- }
338
- node.body.forEach(s => this.genStmt(s));
339
- this.out();
340
- this._loopDepth--;
341
- this.emit('}');
342
- }
343
-
344
- // ── labeled statement ────────────────────────────────────────────
345
- genLabeled(node) {
346
- // Inject label onto the inner loop/while statement
347
- node.body.label = node.label;
348
- this.genStmt(node.body);
349
- }
350
-
351
- // ── while ───────────────────────────────────────────────────────
352
- genWhile(node) {
353
- const label = node.label ? `${node.label}: ` : '';
354
- this.emit(`${label}while (${this.genExpr(node.cond)}) {`);
355
- this._loopDepth++;
356
- this.in(); node.body.forEach(s => this.genStmt(s)); this.out();
357
- this._loopDepth--;
358
- this.emit('}');
359
- }
360
-
361
- // ── match / when ────────────────────────────────────────────────
362
- genMatch(node) {
363
- const subj = this.genExpr(node.subject);
364
- const arms = node.arms;
365
-
366
- let hasOpenIf = false; // tracks whether an `if` has been emitted yet
367
-
368
- arms.forEach((arm, i) => {
369
- const pat = arm.pattern;
370
- let cond = null;
371
- let bindings = []; // const declarations to inject into arm body
372
-
373
- if (pat.type === 'WildcardPat') {
374
- cond = null;
375
- } else if (pat.type === 'RangePat') {
376
- const lo = this.genExpr(pat.start);
377
- const hi = this.genExpr(pat.end);
378
- cond = `${subj} >= ${lo} && ${subj} <= ${hi}`;
379
- } else if (pat.type === 'VariantPat') {
380
- // ADT variant: Ok(v), Err(msg), Some(n), None (no bindings)
381
- cond = `${subj}?.__type === "${pat.variant}"`;
382
- bindings = pat.bindings.map((b, idx) => `const ${b} = ${subj}.__args[${idx}];`);
383
- } else {
384
- // LiteralPat — uppercase identifier may be a no-arg ADT constant (None, Empty…)
385
- const patExpr = this.genExpr(pat.value);
386
- if (pat.value.type === 'Identifier' && /^[A-Z]/.test(pat.value.name)) {
387
- cond = `(${subj} === ${patExpr} || ${subj}?.__type === "${pat.value.name}")`;
388
- } else {
389
- cond = `${subj} === ${patExpr}`;
390
- }
391
- }
392
-
393
- // Apply guard: when Pattern if guardExpr
394
- if (arm.guard) {
395
- const guardSrc = this.genExpr(arm.guard);
396
- if (bindings.length > 0) {
397
- // Guard may reference bound vars — use an IIFE so bindings are in scope
398
- const iife = `(function(){ ${bindings.map(b => b.replace('const ', 'var ')).join(' ')} return (${guardSrc}); }())`;
399
- cond = cond ? `(${cond}) && ${iife}` : iife;
400
- } else {
401
- cond = cond ? `(${cond}) && (${guardSrc})` : guardSrc;
402
- }
403
- }
404
-
405
- // FIX: track whether we've emitted an `if` yet so that a wildcard
406
- // arm appearing first doesn't produce a bare `else {` (invalid JS).
407
- if (!hasOpenIf && cond) {
408
- this.emit(`if (${cond}) {`);
409
- hasOpenIf = true;
410
- } else if (!hasOpenIf && !cond) {
411
- // Wildcard/default as first (or only) arm — emit `if (true)` so the
412
- // subsequent `else if` / `else` arms (if any) have a valid anchor.
413
- this.emit(`if (true) {`);
414
- hasOpenIf = true;
415
- } else if (hasOpenIf && cond) {
416
- this.emitRaw(this.i() + `else if (${cond}) {`);
417
- } else {
418
- this.emitRaw(this.i() + 'else {');
419
- }
420
- this.in();
421
- for (const b of bindings) this.emit(b);
422
- if (arm.inline) {
423
- const exprSrc = this.genExpr(arm.body[0].expr);
424
- if (this._loopDepth > 0) {
425
- // Inside a for/while — use plain statement (return would escape the loop)
426
- this.emit(`${exprSrc};`);
427
- } else {
428
- // Inside a function body — use return to produce the value
429
- this.emit(`return ${exprSrc};`);
430
- }
431
- } else {
432
- arm.body.forEach(s => this.genStmt(s));
433
- }
434
- this.out();
435
- this.emit('}');
436
- });
437
- }
438
-
439
- // ── interface → erased at runtime (structural typing comment) ──
440
- genInterfaceDecl(node) {
441
- this.blank();
442
- this.emit(`// interface ${node.name}`);
443
- this.blank();
444
- }
445
-
446
- // ── enum → Object.freeze({ Key: value, ... }) ───────────────────
447
- genEnumDecl(node) {
448
- this.blank();
449
- const pairs = node.members.map(m => {
450
- const val = this.genExpr(m.value);
451
- return `${m.name}: ${val}`;
452
- }).join(', ');
453
- this.emit(`const ${node.name} = Object.freeze({ ${pairs} });`);
454
- this.blank();
455
- }
456
-
457
- // ── type / ADT ──────────────────────────────────────────────────
458
- genTypeDecl(node) {
459
- this.blank();
460
- this.emit(`// ADT type: ${node.name}`);
461
- for (const v of node.variants) {
462
- if (v.fields.length === 0) {
463
- this.emit(`const ${v.name} = Object.freeze({ __type: "${v.name}", __args: [] });`);
464
- } else {
465
- const params = v.fields.join(', ');
466
- const argsArr = `[${v.fields.join(', ')}]`;
467
- const fieldsObj = v.fields.map(f => `${f}: ${f}`).join(', ');
468
- this.emit(`function ${v.name}(${params}) { return Object.freeze({ __type: "${v.name}", __args: ${argsArr}, ${fieldsObj} }); }`);
469
- }
470
- }
471
- this.blank();
472
- }
473
-
474
- // ── return ──────────────────────────────────────────────────────
475
- genReturn(node) {
476
- if (node.value) this.emit(`return ${this.genExpr(node.value)};`);
477
- else this.emit('return;');
478
- }
479
-
480
- // ── try / catch / finally ───────────────────────────────────────
481
- genTryCatch(node) {
482
- this.emit('try {');
483
- this.in(); node.tryBody.forEach(s => this.genStmt(s)); this.out();
484
- this.emit('}');
485
-
486
- if (node.catchBody) {
487
- const param = node.catchParam ? `(${node.catchParam})` : '(_err)';
488
- this.emitRaw(this.i() + `catch ${param} {`);
489
- this.in(); node.catchBody.forEach(s => this.genStmt(s)); this.out();
490
- this.emit('}');
491
- }
492
-
493
- if (node.finallyBody) {
494
- this.emitRaw(this.i() + 'finally {');
495
- this.in(); node.finallyBody.forEach(s => this.genStmt(s)); this.out();
496
- this.emit('}');
497
- }
498
- }
499
-
500
- // ── throw ───────────────────────────────────────────────────────
501
- genThrow(node) {
502
- this.emit(`throw ${this.genExpr(node.value)};`);
503
- }
504
-
505
- // ── do...while ──────────────────────────────────────────────────
506
- genDoWhile(node) {
507
- this.emit('do {');
508
- this.in();
509
- for (const s of node.body) this.genStmt(s);
510
- this.out();
511
- this.emit(`} while (${this.genExpr(node.cond)});`);
512
- }
513
-
514
- // ── import ──────────────────────────────────────────────────────
515
- genImport(node) {
516
- const src = typeof node.source === 'string' ? node.source : String(node.source);
517
- if (node.namespaceName) {
518
- this.emit(`const ${node.namespaceName} = require("${src}");`);
519
- } else if (node.defaultName) {
520
- this.emit(`const ${node.defaultName} = require("${src}");`);
521
- } else if (node.names.length) {
522
- const parts = node.names.map(n => {
523
- if (typeof n === 'string') return n;
524
- return n.name !== n.alias ? `${n.name}: ${n.alias}` : n.name;
525
- }).join(', ');
526
- this.emit(`const { ${parts} } = require("${src}");`);
527
- }
528
- }
529
-
530
- // ── export ──────────────────────────────────────────────────────
531
- genExport(node) {
532
- if (node.isDefault) {
533
- this.emit(`module.exports = ${this.genExpr(node.decl)};`);
534
- return;
535
- }
536
- const decl = node.decl;
537
- switch (decl.type) {
538
- case 'FnDecl': {
539
- const asyncKw = decl.async ? 'async ' : '';
540
- const params = decl.params.map(p =>
541
- p.rest ? `...${p.name}` :
542
- p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
543
- p.name
544
- ).join(', ');
545
- if (decl.inline) {
546
- this.emit(`${asyncKw}function ${decl.name}(${params}) { return ${this.genExpr(decl.body)}; }`);
547
- } else {
548
- this.emit(`${asyncKw}function ${decl.name}(${params}) {`);
549
- this.in(); decl.body.forEach(s => this.genStmt(s)); this.out();
550
- this.emit('}');
551
- }
552
- this.emit(`module.exports.${decl.name} = ${decl.name};`);
553
- break;
554
- }
555
- case 'ClassDecl':
556
- this.genClass(decl);
557
- this.emit(`module.exports.${decl.name} = ${decl.name};`);
558
- break;
559
- case 'TypeDecl':
560
- this.genTypeDecl(decl);
561
- for (const v of decl.variants) this.emit(`module.exports.${v.name} = ${v.name};`);
562
- break;
563
- case 'InterfaceDecl':
564
- this.genInterfaceDecl(decl);
565
- break;
566
- case 'EnumDecl':
567
- this.genEnumDecl(decl);
568
- this.emit(`module.exports.${decl.name} = ${decl.name};`);
569
- break;
570
- case 'VarDecl': {
571
- const kw = decl.kind === 'val' ? 'const' : 'let';
572
- this.emit(`${kw} ${decl.name} = ${this.genExpr(decl.init)};`);
573
- this.emit(`module.exports.${decl.name} = ${decl.name};`);
574
- break;
575
- }
576
- default:
577
- this.genStmt(decl);
578
- }
579
- }
580
-
581
- // ── Expressions ─────────────────────────────────────────────────
582
- genExpr(node) {
583
- if (!node) return 'undefined';
584
- switch (node.type) {
585
- case 'NumberLit': return String(node.value);
586
- case 'BoolLit': return String(node.value);
587
- case 'NullLit': return 'null';
588
- case 'SelfExpr': return 'this';
589
- case 'Identifier': return node.name === 'print' ? 'console.log' : node.name;
590
-
591
- case 'StringLit':
592
- return JSON.stringify(node.value);
593
-
594
- case 'RegexLit':
595
- return `/${node.value.pattern}/${node.value.flags}`;
596
-
597
- case 'TemplateLit':
598
- return this.genTemplate(node.parts);
599
-
600
- case 'BinaryExpr':
601
- return `(${this.genExpr(node.left)} ${node.op} ${this.genExpr(node.right)})`;
602
-
603
- case 'UnaryExpr':
604
- return `${node.op}${this.genExpr(node.operand)}`;
605
-
606
- case 'UpdateExpr':
607
- return node.prefix
608
- ? `${node.op}${this.genExpr(node.operand)}`
609
- : `${this.genExpr(node.operand)}${node.op}`;
610
-
611
- case 'TernaryExpr':
612
- return `(${this.genExpr(node.cond)} ? ${this.genExpr(node.then)} : ${this.genExpr(node.else_)})`;
613
-
614
- case 'AssignExpr':
615
- return `${this.genExpr(node.target)} ${node.op} ${this.genExpr(node.value)}`;
616
-
617
- case 'AwaitExpr':
618
- return `await ${this.genExpr(node.operand)}`;
619
-
620
- case 'TypeofExpr':
621
- return `typeof ${this.genExpr(node.operand)}`;
622
-
623
- case 'SpreadExpr':
624
- return `...${this.genExpr(node.expr)}`;
625
-
626
- case 'CallExpr': {
627
- const callee = this.genExpr(node.callee);
628
- const args = node.args.map(a => this.genExpr(a)).join(', ');
629
- return `${callee}(${args})`;
630
- }
631
-
632
- case 'MemberExpr': {
633
- const objSrc = this.genExpr(node.obj);
634
- // Wrap NumberLit in parens: (42).toString() not 42.toString()
635
- const wrappedObj = node.obj.type === 'NumberLit' ? `(${objSrc})` : objSrc;
636
- return `${wrappedObj}.${node.prop}`;
637
- }
638
-
639
- case 'OptMemberExpr':
640
- return `${this.genExpr(node.obj)}?.${node.prop}`;
641
-
642
- case 'OptIndexExpr':
643
- return `${this.genExpr(node.obj)}?.[${this.genExpr(node.idx)}]`;
644
-
645
- case 'OptCallExpr': {
646
- const args = node.args.map(a => this.genExpr(a)).join(', ');
647
- return `${this.genExpr(node.callee)}?.(${args})`;
648
- }
649
-
650
- case 'IndexExpr':
651
- return `${this.genExpr(node.obj)}[${this.genExpr(node.idx)}]`;
652
-
653
- case 'NewExpr': {
654
- const args = node.args.map(a => this.genExpr(a)).join(', ');
655
- return `new ${node.callee}(${args})`;
656
- }
657
-
658
- case 'LambdaExpr': {
659
- const params = node.params.map(p =>
660
- p.rest ? `...${p.name}` : p.name
661
- ).join(', ');
662
- if (node.block) {
663
- // Multiline block body: x ->\n stmts
664
- const saved = this.lines.length;
665
- const savedLevel = this.level;
666
- this.emit(`(${params}) => {`);
667
- this.in();
668
- for (const s of node.body) this.genStmt(s);
669
- this.out();
670
- this.emit('}');
671
- const block = this.lines.splice(saved).map(l => l.trimStart()).join(' ');
672
- this.level = savedLevel;
673
- return block;
674
- }
675
- return `(${params}) => ${this.genExpr(node.body)}`;
676
- }
677
-
678
- case 'FnDecl': {
679
- // Anonymous function expression
680
- const asyncKw = node.async ? 'async ' : '';
681
- const params = node.params.map(p =>
682
- p.rest ? `...${p.name}` :
683
- p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
684
- p.name
685
- ).join(', ');
686
- if (node.inline) {
687
- return `${asyncKw}function(${params}) { return ${this.genExpr(node.body)}; }`;
688
- } else {
689
- // Multi-line anonymous fn — emit as IIFE-like block; wrap in parens
690
- const saved = this.lines.length;
691
- const savedLevel = this.level;
692
- this.emit(`${asyncKw}function(${params}) {`);
693
- this.in();
694
- for (const s of node.body) this.genStmt(s);
695
- this.out();
696
- this.emit('}');
697
- // Pull out the generated lines and return as string
698
- const block = this.lines.splice(saved).map(l => l.trimStart()).join(' ');
699
- this.level = savedLevel;
700
- return block;
701
- }
702
- }
703
-
704
- case 'MatchStmt': {
705
- // match used as an expression — wrap the if/else chain in an IIFE so
706
- // it yields a value. Temporarily zero out _loopDepth so that inline
707
- // arms produce `return expr;` even when the match is nested inside a
708
- // for/while loop.
709
- const saved = this.lines.length;
710
- const savedLevel = this.level;
711
- const savedLoopDepth = this._loopDepth;
712
- this._loopDepth = 0;
713
- this.emit('(() => {');
714
- this.in();
715
- this.genMatch(node);
716
- this.out();
717
- this.emit('})()');
718
- this._loopDepth = savedLoopDepth;
719
- const block = this.lines.splice(saved).map(l => l.trimStart()).join(' ');
720
- this.level = savedLevel;
721
- return block;
722
- }
723
-
724
- case 'ArrayExpr':
725
- return `[${node.items.map(i => this.genExpr(i)).join(', ')}]`;
726
-
727
- case 'ObjectExpr': {
728
- const pairs = node.pairs.map(p => {
729
- if (p.spread) return `...${this.genExpr(p.value)}`;
730
- // Computed key: { [expr]: value }
731
- if (p.computed) return `[${this.genExpr(p.keyExpr)}]: ${this.genExpr(p.value)}`;
732
- // Auto-quote keys that aren't valid JS identifiers (e.g. "Content-Type")
733
- const isIdent = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.key);
734
- const keyStr = isIdent ? p.key : `"${p.key}"`;
735
- // Shorthand detection: { name: name } → { name } (only for plain identifiers)
736
- if (isIdent && p.value && p.value.type === 'Identifier' && p.value.name === p.key)
737
- return p.key;
738
- return `${keyStr}: ${this.genExpr(p.value)}`;
739
- }).join(', ');
740
- return `{ ${pairs} }`;
741
- }
742
-
743
- case 'RangeExpr':
744
- // When used standalone (not in for-in), generate an array
745
- return `Array.from({ length: ${this.genExpr(node.end)} - ${this.genExpr(node.start)} }, (_, i) => i + ${this.genExpr(node.start)})`;
746
-
747
- case 'PipeExpr':
748
- return this.genPipe(node);
749
-
750
- // ── Type system expressions (compile-time only, runtime pass-through) ──
751
- case 'CastExpr':
752
- // expr as Type → just the expr at runtime
753
- return this.genExpr(node.expr);
754
-
755
- case 'AsConstExpr':
756
- // expr as const → Object.freeze(expr)
757
- return `Object.freeze(${this.genExpr(node.expr)})`;
758
-
759
- case 'SatisfiesExpr':
760
- // expr satisfies Type → just the expr at runtime
761
- return this.genExpr(node.expr);
762
-
763
- case 'IsExpr':
764
- // expr is Type → just the expr at runtime (used as a type guard result)
765
- return this.genExpr(node.expr);
766
-
767
- case 'NonNullExpr':
768
- // expr! → just the expr at runtime (non-null assertion)
769
- return this.genExpr(node.expr);
770
-
771
- default:
772
- throw new CodeGenError(`Unknown expression: ${node.type}`, node);
773
- }
774
- }
775
-
776
- // ── Template literal: "Hello, {name}!" ──────────────────────────
777
- // Each {expr} part is raw Flux source — parse + compile it.
778
- // Supports format specs: {price:.2f} {n:,} {pct:.1%}
779
- genTemplate(parts) {
780
- const { Lexer } = require('./lexer');
781
- const { Parser } = require('./parser');
782
-
783
- let result = '`';
784
- for (const p of parts) {
785
- if (p.type === 'text') {
786
- result += p.value.replace(/`/g, '\\`').replace(/\$/g, '\\$');
787
- } else {
788
- // Check for format spec: {value:.2f} or {n:,}
789
- const fmtInfo = extractFormatSpec(p.value);
790
- const exprSrc = fmtInfo ? fmtInfo.expr : p.value;
791
- const fmt = fmtInfo ? fmtInfo.fmt : null;
792
-
793
- try {
794
- const tokens = new Lexer(exprSrc).tokenize();
795
- const expr = new Parser(tokens).parseExpr();
796
- const gen = this.genExpr(expr);
797
- if (fmt) {
798
- this._needsFmt = true;
799
- result += `\${_fmt(${gen}, ${JSON.stringify(fmt)})}`;
800
- } else {
801
- result += `\${${gen}}`;
802
- }
803
- } catch (_) {
804
- result += `\${${p.value}}`;
805
- }
806
- }
807
- }
808
- result += '`';
809
- return result;
810
- }
811
-
812
- // ── Pipe: a |> f → f(a)
813
- // a |> f(b, c) → f(a, b, c) ─────────────────────────
814
- genPipe(node) {
815
- const left = this.genExpr(node.left);
816
- const right = node.right;
817
-
818
- if (right.type === 'Identifier') {
819
- const fn = this.genExpr(right);
820
- return `${fn}(${left})`;
821
- }
822
- if (right.type === 'CallExpr') {
823
- const fn = this.genExpr(right.callee);
824
- const rest = right.args.map(a => this.genExpr(a)).join(', ');
825
- return rest ? `${fn}(${left}, ${rest})` : `${fn}(${left})`;
826
- }
827
- // Lambda / other expression: treat as call
828
- return `(${this.genExpr(right)})(${left})`;
829
- }
830
- }
831
-
832
- module.exports = { CodeGenerator, CodeGenError };