@xnoxs/flux-lang 3.1.1

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +1089 -0
  3. package/bin/flux.js +1397 -0
  4. package/dist/flux.cjs.js +6664 -0
  5. package/dist/flux.esm.js +6674 -0
  6. package/dist/flux.min.js +263 -0
  7. package/index.d.ts +202 -0
  8. package/index.js +26 -0
  9. package/package.json +77 -0
  10. package/scripts/build.js +76 -0
  11. package/src/bundler.js +216 -0
  12. package/src/checker.js +322 -0
  13. package/src/codegen.js +785 -0
  14. package/src/css-preprocessor.js +399 -0
  15. package/src/formatter.js +140 -0
  16. package/src/jsx.js +480 -0
  17. package/src/lexer.js +518 -0
  18. package/src/linter.js +758 -0
  19. package/src/mangler.js +280 -0
  20. package/src/parser.js +1671 -0
  21. package/src/self/bundler.flux +167 -0
  22. package/src/self/bundler.js +187 -0
  23. package/src/self/checker.flux +249 -0
  24. package/src/self/checker.js +338 -0
  25. package/src/self/codegen.flux +555 -0
  26. package/src/self/codegen.js +784 -0
  27. package/src/self/css-preprocessor.flux +373 -0
  28. package/src/self/css-preprocessor.js +387 -0
  29. package/src/self/formatter.flux +93 -0
  30. package/src/self/formatter.js +114 -0
  31. package/src/self/jsx.flux +430 -0
  32. package/src/self/jsx.js +396 -0
  33. package/src/self/lexer.flux +529 -0
  34. package/src/self/lexer.js +709 -0
  35. package/src/self/lexer.stage2.js +700 -0
  36. package/src/self/linter.flux +515 -0
  37. package/src/self/linter.js +804 -0
  38. package/src/self/mangler.flux +253 -0
  39. package/src/self/mangler.js +348 -0
  40. package/src/self/parser.flux +1146 -0
  41. package/src/self/parser.js +1571 -0
  42. package/src/self/sourcemap.flux +66 -0
  43. package/src/self/sourcemap.js +72 -0
  44. package/src/self/stdlib.flux +356 -0
  45. package/src/self/stdlib.js +396 -0
  46. package/src/self/test-runner.flux +201 -0
  47. package/src/self/test-runner.js +132 -0
  48. package/src/self/transpiler.flux +123 -0
  49. package/src/self/transpiler.js +83 -0
  50. package/src/self/type-checker.flux +821 -0
  51. package/src/self/type-checker.js +1106 -0
  52. package/src/sourcemap.js +82 -0
  53. package/src/stdlib.js +436 -0
  54. package/src/test-runner.js +239 -0
  55. package/src/transpiler.js +172 -0
  56. package/src/type-checker.js +1206 -0
package/src/codegen.js ADDED
@@ -0,0 +1,785 @@
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.1.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
+ }
193
+
194
+ // ── class ──────────────────────────────────────────────────────
195
+ genClass(node) {
196
+ const ext = node.superClass ? ` extends ${node.superClass}` : '';
197
+ this.emit(`class ${node.name}${ext} {`);
198
+ this.in();
199
+
200
+ // Constructor
201
+ const allFields = getAllFields(node.name, this.clsReg);
202
+ if (allFields.length > 0) {
203
+ const params = allFields.map(f => f.name).join(', ');
204
+ this.emit(`constructor(${params}) {`);
205
+ this.in();
206
+
207
+ // super(...parentFields) if extending
208
+ if (node.superClass && this.clsReg[node.superClass]) {
209
+ const parentFields = getAllFields(node.superClass, this.clsReg);
210
+ if (parentFields.length > 0)
211
+ this.emit(`super(${parentFields.map(f => f.name).join(', ')});`);
212
+ }
213
+
214
+ // Assign own fields only
215
+ for (const f of node.fields) this.emit(`this.${f.name} = ${f.name};`);
216
+ this.out();
217
+ this.emit('}');
218
+ this.blank();
219
+ }
220
+
221
+ // Methods
222
+ for (const m of node.methods) {
223
+ const asyncKw = m.async ? 'async ' : '';
224
+ const staticKw = (m.modifiers && m.modifiers.has('static')) ? 'static ' : '';
225
+ const getsetKw = m.getset ? `${m.getset} ` : '';
226
+ const params = m.params.map(p =>
227
+ p.rest ? `...${p.name}` :
228
+ p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
229
+ p.name
230
+ ).join(', ');
231
+ if (m.inline) {
232
+ this.emit(`${staticKw}${asyncKw}${getsetKw}${m.name}(${params}) { return ${this.genExpr(m.body)}; }`);
233
+ } else {
234
+ this.emit(`${staticKw}${asyncKw}${getsetKw}${m.name}(${params}) {`);
235
+ this.in();
236
+ for (const s of m.body) this.genStmt(s);
237
+ this.out();
238
+ this.emit('}');
239
+ }
240
+ this.blank();
241
+ }
242
+
243
+ this.out();
244
+ this.emit('}');
245
+ this.blank();
246
+ }
247
+
248
+ // ── if / else if / else ─────────────────────────────────────────
249
+ genIf(node) {
250
+ this.emit(`if (${this.genExpr(node.cond)}) {`);
251
+ this.in(); node.then.forEach(s => this.genStmt(s)); this.out();
252
+ this.emit('}');
253
+
254
+ for (const ei of node.elseifs) {
255
+ this.emitRaw(this.i() + `else if (${this.genExpr(ei.cond)}) {`);
256
+ this.in(); ei.body.forEach(s => this.genStmt(s)); this.out();
257
+ this.emit('}');
258
+ }
259
+
260
+ if (node.else_) {
261
+ this.emitRaw(this.i() + 'else {');
262
+ this.in(); node.else_.forEach(s => this.genStmt(s)); this.out();
263
+ this.emit('}');
264
+ }
265
+ }
266
+
267
+ // ── for x in iter / for i in 0..10 / for [a,b] in arr ──────────
268
+ genFor(node) {
269
+ const iter = node.iter;
270
+ const label = node.label ? `${node.label}: ` : '';
271
+
272
+ if (iter.type === 'RangeExpr') {
273
+ const s = this.genExpr(iter.start);
274
+ const e = this.genExpr(iter.end);
275
+ this.emit(`${label}for (let ${node.var} = ${s}; ${node.var} < ${e}; ${node.var}++) {`);
276
+ } else if (node.isAwait) {
277
+ this.emit(`${label}for await (const ${node.var} of ${this.genExpr(iter)}) {`);
278
+ } else {
279
+ this.emit(`${label}for (const ${node.var} of ${this.genExpr(iter)}) {`);
280
+ }
281
+ this._loopDepth++;
282
+ this.in();
283
+ // Destructuring in loop var: for [a, b] in arr
284
+ if (node.varPattern) {
285
+ const p = node.varPattern;
286
+ if (p.type === 'array') {
287
+ const names = p.names.map(n => n.rest ? `...${n.name}` : n.name).join(', ');
288
+ this.emit(`const [${names}] = ${node.var};`);
289
+ }
290
+ }
291
+ node.body.forEach(s => this.genStmt(s));
292
+ this.out();
293
+ this._loopDepth--;
294
+ this.emit('}');
295
+ }
296
+
297
+ // ── labeled statement ────────────────────────────────────────────
298
+ genLabeled(node) {
299
+ // Inject label onto the inner loop/while statement
300
+ node.body.label = node.label;
301
+ this.genStmt(node.body);
302
+ }
303
+
304
+ // ── while ───────────────────────────────────────────────────────
305
+ genWhile(node) {
306
+ const label = node.label ? `${node.label}: ` : '';
307
+ this.emit(`${label}while (${this.genExpr(node.cond)}) {`);
308
+ this._loopDepth++;
309
+ this.in(); node.body.forEach(s => this.genStmt(s)); this.out();
310
+ this._loopDepth--;
311
+ this.emit('}');
312
+ }
313
+
314
+ // ── match / when ────────────────────────────────────────────────
315
+ genMatch(node) {
316
+ const subj = this.genExpr(node.subject);
317
+ const arms = node.arms;
318
+
319
+ let hasOpenIf = false; // tracks whether an `if` has been emitted yet
320
+
321
+ arms.forEach((arm, i) => {
322
+ const pat = arm.pattern;
323
+ let cond = null;
324
+ let bindings = []; // const declarations to inject into arm body
325
+
326
+ if (pat.type === 'WildcardPat') {
327
+ cond = null;
328
+ } else if (pat.type === 'RangePat') {
329
+ const lo = this.genExpr(pat.start);
330
+ const hi = this.genExpr(pat.end);
331
+ cond = `${subj} >= ${lo} && ${subj} <= ${hi}`;
332
+ } else if (pat.type === 'VariantPat') {
333
+ // ADT variant: Ok(v), Err(msg), Some(n), None (no bindings)
334
+ cond = `${subj}?.__type === "${pat.variant}"`;
335
+ bindings = pat.bindings.map((b, idx) => `const ${b} = ${subj}.__args[${idx}];`);
336
+ } else {
337
+ // LiteralPat — uppercase identifier may be a no-arg ADT constant (None, Empty…)
338
+ const patExpr = this.genExpr(pat.value);
339
+ if (pat.value.type === 'Identifier' && /^[A-Z]/.test(pat.value.name)) {
340
+ cond = `(${subj} === ${patExpr} || ${subj}?.__type === "${pat.value.name}")`;
341
+ } else {
342
+ cond = `${subj} === ${patExpr}`;
343
+ }
344
+ }
345
+
346
+ // Apply guard: when Pattern if guardExpr
347
+ if (arm.guard) {
348
+ const guardSrc = this.genExpr(arm.guard);
349
+ if (bindings.length > 0) {
350
+ // Guard may reference bound vars — use an IIFE so bindings are in scope
351
+ const iife = `(function(){ ${bindings.map(b => b.replace('const ', 'var ')).join(' ')} return (${guardSrc}); }())`;
352
+ cond = cond ? `(${cond}) && ${iife}` : iife;
353
+ } else {
354
+ cond = cond ? `(${cond}) && (${guardSrc})` : guardSrc;
355
+ }
356
+ }
357
+
358
+ // FIX: track whether we've emitted an `if` yet so that a wildcard
359
+ // arm appearing first doesn't produce a bare `else {` (invalid JS).
360
+ if (!hasOpenIf && cond) {
361
+ this.emit(`if (${cond}) {`);
362
+ hasOpenIf = true;
363
+ } else if (!hasOpenIf && !cond) {
364
+ // Wildcard/default as first (or only) arm — emit `if (true)` so the
365
+ // subsequent `else if` / `else` arms (if any) have a valid anchor.
366
+ this.emit(`if (true) {`);
367
+ hasOpenIf = true;
368
+ } else if (hasOpenIf && cond) {
369
+ this.emitRaw(this.i() + `else if (${cond}) {`);
370
+ } else {
371
+ this.emitRaw(this.i() + 'else {');
372
+ }
373
+ this.in();
374
+ for (const b of bindings) this.emit(b);
375
+ if (arm.inline) {
376
+ const exprSrc = this.genExpr(arm.body[0].expr);
377
+ if (this._loopDepth > 0) {
378
+ // Inside a for/while — use plain statement (return would escape the loop)
379
+ this.emit(`${exprSrc};`);
380
+ } else {
381
+ // Inside a function body — use return to produce the value
382
+ this.emit(`return ${exprSrc};`);
383
+ }
384
+ } else {
385
+ arm.body.forEach(s => this.genStmt(s));
386
+ }
387
+ this.out();
388
+ this.emit('}');
389
+ });
390
+ }
391
+
392
+ // ── interface → erased at runtime (structural typing comment) ──
393
+ genInterfaceDecl(node) {
394
+ this.blank();
395
+ this.emit(`// interface ${node.name}`);
396
+ this.blank();
397
+ }
398
+
399
+ // ── enum → Object.freeze({ Key: value, ... }) ───────────────────
400
+ genEnumDecl(node) {
401
+ this.blank();
402
+ const pairs = node.members.map(m => {
403
+ const val = this.genExpr(m.value);
404
+ return `${m.name}: ${val}`;
405
+ }).join(', ');
406
+ this.emit(`const ${node.name} = Object.freeze({ ${pairs} });`);
407
+ this.blank();
408
+ }
409
+
410
+ // ── type / ADT ──────────────────────────────────────────────────
411
+ genTypeDecl(node) {
412
+ this.blank();
413
+ this.emit(`// ADT type: ${node.name}`);
414
+ for (const v of node.variants) {
415
+ if (v.fields.length === 0) {
416
+ this.emit(`const ${v.name} = Object.freeze({ __type: "${v.name}", __args: [] });`);
417
+ } else {
418
+ const params = v.fields.join(', ');
419
+ const argsArr = `[${v.fields.join(', ')}]`;
420
+ const fieldsObj = v.fields.map(f => `${f}: ${f}`).join(', ');
421
+ this.emit(`function ${v.name}(${params}) { return Object.freeze({ __type: "${v.name}", __args: ${argsArr}, ${fieldsObj} }); }`);
422
+ }
423
+ }
424
+ this.blank();
425
+ }
426
+
427
+ // ── return ──────────────────────────────────────────────────────
428
+ genReturn(node) {
429
+ if (node.value) this.emit(`return ${this.genExpr(node.value)};`);
430
+ else this.emit('return;');
431
+ }
432
+
433
+ // ── try / catch / finally ───────────────────────────────────────
434
+ genTryCatch(node) {
435
+ this.emit('try {');
436
+ this.in(); node.tryBody.forEach(s => this.genStmt(s)); this.out();
437
+ this.emit('}');
438
+
439
+ if (node.catchBody) {
440
+ const param = node.catchParam ? `(${node.catchParam})` : '(_err)';
441
+ this.emitRaw(this.i() + `catch ${param} {`);
442
+ this.in(); node.catchBody.forEach(s => this.genStmt(s)); this.out();
443
+ this.emit('}');
444
+ }
445
+
446
+ if (node.finallyBody) {
447
+ this.emitRaw(this.i() + 'finally {');
448
+ this.in(); node.finallyBody.forEach(s => this.genStmt(s)); this.out();
449
+ this.emit('}');
450
+ }
451
+ }
452
+
453
+ // ── throw ───────────────────────────────────────────────────────
454
+ genThrow(node) {
455
+ this.emit(`throw ${this.genExpr(node.value)};`);
456
+ }
457
+
458
+ // ── do...while ──────────────────────────────────────────────────
459
+ genDoWhile(node) {
460
+ this.emit('do {');
461
+ this.in();
462
+ for (const s of node.body) this.genStmt(s);
463
+ this.out();
464
+ this.emit(`} while (${this.genExpr(node.cond)});`);
465
+ }
466
+
467
+ // ── import ──────────────────────────────────────────────────────
468
+ genImport(node) {
469
+ const src = typeof node.source === 'string' ? node.source : String(node.source);
470
+ if (node.namespaceName) {
471
+ this.emit(`const ${node.namespaceName} = require("${src}");`);
472
+ } else if (node.defaultName) {
473
+ this.emit(`const ${node.defaultName} = require("${src}");`);
474
+ } else if (node.names.length) {
475
+ const parts = node.names.map(n => {
476
+ if (typeof n === 'string') return n;
477
+ return n.name !== n.alias ? `${n.name}: ${n.alias}` : n.name;
478
+ }).join(', ');
479
+ this.emit(`const { ${parts} } = require("${src}");`);
480
+ }
481
+ }
482
+
483
+ // ── export ──────────────────────────────────────────────────────
484
+ genExport(node) {
485
+ if (node.isDefault) {
486
+ this.emit(`module.exports = ${this.genExpr(node.decl)};`);
487
+ return;
488
+ }
489
+ const decl = node.decl;
490
+ switch (decl.type) {
491
+ case 'FnDecl': {
492
+ const asyncKw = decl.async ? 'async ' : '';
493
+ const params = decl.params.map(p =>
494
+ p.rest ? `...${p.name}` :
495
+ p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
496
+ p.name
497
+ ).join(', ');
498
+ if (decl.inline) {
499
+ this.emit(`${asyncKw}function ${decl.name}(${params}) { return ${this.genExpr(decl.body)}; }`);
500
+ } else {
501
+ this.emit(`${asyncKw}function ${decl.name}(${params}) {`);
502
+ this.in(); decl.body.forEach(s => this.genStmt(s)); this.out();
503
+ this.emit('}');
504
+ }
505
+ this.emit(`module.exports.${decl.name} = ${decl.name};`);
506
+ break;
507
+ }
508
+ case 'ClassDecl':
509
+ this.genClass(decl);
510
+ this.emit(`module.exports.${decl.name} = ${decl.name};`);
511
+ break;
512
+ case 'TypeDecl':
513
+ this.genTypeDecl(decl);
514
+ for (const v of decl.variants) this.emit(`module.exports.${v.name} = ${v.name};`);
515
+ break;
516
+ case 'InterfaceDecl':
517
+ this.genInterfaceDecl(decl);
518
+ break;
519
+ case 'EnumDecl':
520
+ this.genEnumDecl(decl);
521
+ this.emit(`module.exports.${decl.name} = ${decl.name};`);
522
+ break;
523
+ case 'VarDecl': {
524
+ const kw = decl.kind === 'val' ? 'const' : 'let';
525
+ this.emit(`${kw} ${decl.name} = ${this.genExpr(decl.init)};`);
526
+ this.emit(`module.exports.${decl.name} = ${decl.name};`);
527
+ break;
528
+ }
529
+ default:
530
+ this.genStmt(decl);
531
+ }
532
+ }
533
+
534
+ // ── Expressions ─────────────────────────────────────────────────
535
+ genExpr(node) {
536
+ if (!node) return 'undefined';
537
+ switch (node.type) {
538
+ case 'NumberLit': return String(node.value);
539
+ case 'BoolLit': return String(node.value);
540
+ case 'NullLit': return 'null';
541
+ case 'SelfExpr': return 'this';
542
+ case 'Identifier': return node.name === 'print' ? 'console.log' : node.name;
543
+
544
+ case 'StringLit':
545
+ return JSON.stringify(node.value);
546
+
547
+ case 'RegexLit':
548
+ return `/${node.value.pattern}/${node.value.flags}`;
549
+
550
+ case 'TemplateLit':
551
+ return this.genTemplate(node.parts);
552
+
553
+ case 'BinaryExpr':
554
+ return `(${this.genExpr(node.left)} ${node.op} ${this.genExpr(node.right)})`;
555
+
556
+ case 'UnaryExpr':
557
+ return `${node.op}${this.genExpr(node.operand)}`;
558
+
559
+ case 'UpdateExpr':
560
+ return node.prefix
561
+ ? `${node.op}${this.genExpr(node.operand)}`
562
+ : `${this.genExpr(node.operand)}${node.op}`;
563
+
564
+ case 'TernaryExpr':
565
+ return `(${this.genExpr(node.cond)} ? ${this.genExpr(node.then)} : ${this.genExpr(node.else_)})`;
566
+
567
+ case 'AssignExpr':
568
+ return `${this.genExpr(node.target)} ${node.op} ${this.genExpr(node.value)}`;
569
+
570
+ case 'AwaitExpr':
571
+ return `await ${this.genExpr(node.operand)}`;
572
+
573
+ case 'TypeofExpr':
574
+ return `typeof ${this.genExpr(node.operand)}`;
575
+
576
+ case 'SpreadExpr':
577
+ return `...${this.genExpr(node.expr)}`;
578
+
579
+ case 'CallExpr': {
580
+ const callee = this.genExpr(node.callee);
581
+ const args = node.args.map(a => this.genExpr(a)).join(', ');
582
+ return `${callee}(${args})`;
583
+ }
584
+
585
+ case 'MemberExpr': {
586
+ const objSrc = this.genExpr(node.obj);
587
+ // Wrap NumberLit in parens: (42).toString() not 42.toString()
588
+ const wrappedObj = node.obj.type === 'NumberLit' ? `(${objSrc})` : objSrc;
589
+ return `${wrappedObj}.${node.prop}`;
590
+ }
591
+
592
+ case 'OptMemberExpr':
593
+ return `${this.genExpr(node.obj)}?.${node.prop}`;
594
+
595
+ case 'OptIndexExpr':
596
+ return `${this.genExpr(node.obj)}?.[${this.genExpr(node.idx)}]`;
597
+
598
+ case 'OptCallExpr': {
599
+ const args = node.args.map(a => this.genExpr(a)).join(', ');
600
+ return `${this.genExpr(node.callee)}?.(${args})`;
601
+ }
602
+
603
+ case 'IndexExpr':
604
+ return `${this.genExpr(node.obj)}[${this.genExpr(node.idx)}]`;
605
+
606
+ case 'NewExpr': {
607
+ const args = node.args.map(a => this.genExpr(a)).join(', ');
608
+ return `new ${node.callee}(${args})`;
609
+ }
610
+
611
+ case 'LambdaExpr': {
612
+ const params = node.params.map(p =>
613
+ p.rest ? `...${p.name}` : p.name
614
+ ).join(', ');
615
+ if (node.block) {
616
+ // Multiline block body: x ->\n stmts
617
+ const saved = this.lines.length;
618
+ const savedLevel = this.level;
619
+ this.emit(`(${params}) => {`);
620
+ this.in();
621
+ for (const s of node.body) this.genStmt(s);
622
+ this.out();
623
+ this.emit('}');
624
+ const block = this.lines.splice(saved).map(l => l.trimStart()).join(' ');
625
+ this.level = savedLevel;
626
+ return block;
627
+ }
628
+ return `(${params}) => ${this.genExpr(node.body)}`;
629
+ }
630
+
631
+ case 'FnDecl': {
632
+ // Anonymous function expression
633
+ const asyncKw = node.async ? 'async ' : '';
634
+ const params = node.params.map(p =>
635
+ p.rest ? `...${p.name}` :
636
+ p.defaultVal ? `${p.name} = ${this.genExpr(p.defaultVal)}` :
637
+ p.name
638
+ ).join(', ');
639
+ if (node.inline) {
640
+ return `${asyncKw}function(${params}) { return ${this.genExpr(node.body)}; }`;
641
+ } else {
642
+ // Multi-line anonymous fn — emit as IIFE-like block; wrap in parens
643
+ const saved = this.lines.length;
644
+ const savedLevel = this.level;
645
+ this.emit(`${asyncKw}function(${params}) {`);
646
+ this.in();
647
+ for (const s of node.body) this.genStmt(s);
648
+ this.out();
649
+ this.emit('}');
650
+ // Pull out the generated lines and return as string
651
+ const block = this.lines.splice(saved).map(l => l.trimStart()).join(' ');
652
+ this.level = savedLevel;
653
+ return block;
654
+ }
655
+ }
656
+
657
+ case 'MatchStmt': {
658
+ // match used as an expression — wrap the if/else chain in an IIFE so
659
+ // it yields a value. Temporarily zero out _loopDepth so that inline
660
+ // arms produce `return expr;` even when the match is nested inside a
661
+ // for/while loop.
662
+ const saved = this.lines.length;
663
+ const savedLevel = this.level;
664
+ const savedLoopDepth = this._loopDepth;
665
+ this._loopDepth = 0;
666
+ this.emit('(() => {');
667
+ this.in();
668
+ this.genMatch(node);
669
+ this.out();
670
+ this.emit('})()');
671
+ this._loopDepth = savedLoopDepth;
672
+ const block = this.lines.splice(saved).map(l => l.trimStart()).join(' ');
673
+ this.level = savedLevel;
674
+ return block;
675
+ }
676
+
677
+ case 'ArrayExpr':
678
+ return `[${node.items.map(i => this.genExpr(i)).join(', ')}]`;
679
+
680
+ case 'ObjectExpr': {
681
+ const pairs = node.pairs.map(p => {
682
+ if (p.spread) return `...${this.genExpr(p.value)}`;
683
+ // Computed key: { [expr]: value }
684
+ if (p.computed) return `[${this.genExpr(p.keyExpr)}]: ${this.genExpr(p.value)}`;
685
+ // Auto-quote keys that aren't valid JS identifiers (e.g. "Content-Type")
686
+ const isIdent = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.key);
687
+ const keyStr = isIdent ? p.key : `"${p.key}"`;
688
+ // Shorthand detection: { name: name } → { name } (only for plain identifiers)
689
+ if (isIdent && p.value && p.value.type === 'Identifier' && p.value.name === p.key)
690
+ return p.key;
691
+ return `${keyStr}: ${this.genExpr(p.value)}`;
692
+ }).join(', ');
693
+ return `{ ${pairs} }`;
694
+ }
695
+
696
+ case 'RangeExpr':
697
+ // When used standalone (not in for-in), generate an array
698
+ return `Array.from({ length: ${this.genExpr(node.end)} - ${this.genExpr(node.start)} }, (_, i) => i + ${this.genExpr(node.start)})`;
699
+
700
+ case 'PipeExpr':
701
+ return this.genPipe(node);
702
+
703
+ // ── Type system expressions (compile-time only, runtime pass-through) ──
704
+ case 'CastExpr':
705
+ // expr as Type → just the expr at runtime
706
+ return this.genExpr(node.expr);
707
+
708
+ case 'AsConstExpr':
709
+ // expr as const → Object.freeze(expr)
710
+ return `Object.freeze(${this.genExpr(node.expr)})`;
711
+
712
+ case 'SatisfiesExpr':
713
+ // expr satisfies Type → just the expr at runtime
714
+ return this.genExpr(node.expr);
715
+
716
+ case 'IsExpr':
717
+ // expr is Type → just the expr at runtime (used as a type guard result)
718
+ return this.genExpr(node.expr);
719
+
720
+ case 'NonNullExpr':
721
+ // expr! → just the expr at runtime (non-null assertion)
722
+ return this.genExpr(node.expr);
723
+
724
+ default:
725
+ throw new CodeGenError(`Unknown expression: ${node.type}`, node);
726
+ }
727
+ }
728
+
729
+ // ── Template literal: "Hello, {name}!" ──────────────────────────
730
+ // Each {expr} part is raw Flux source — parse + compile it.
731
+ // Supports format specs: {price:.2f} {n:,} {pct:.1%}
732
+ genTemplate(parts) {
733
+ const { Lexer } = require('./lexer');
734
+ const { Parser } = require('./parser');
735
+
736
+ let result = '`';
737
+ for (const p of parts) {
738
+ if (p.type === 'text') {
739
+ result += p.value.replace(/`/g, '\\`').replace(/\$/g, '\\$');
740
+ } else {
741
+ // Check for format spec: {value:.2f} or {n:,}
742
+ const fmtInfo = extractFormatSpec(p.value);
743
+ const exprSrc = fmtInfo ? fmtInfo.expr : p.value;
744
+ const fmt = fmtInfo ? fmtInfo.fmt : null;
745
+
746
+ try {
747
+ const tokens = new Lexer(exprSrc).tokenize();
748
+ const expr = new Parser(tokens).parseExpr();
749
+ const gen = this.genExpr(expr);
750
+ if (fmt) {
751
+ this._needsFmt = true;
752
+ result += `\${_fmt(${gen}, ${JSON.stringify(fmt)})}`;
753
+ } else {
754
+ result += `\${${gen}}`;
755
+ }
756
+ } catch (_) {
757
+ result += `\${${p.value}}`;
758
+ }
759
+ }
760
+ }
761
+ result += '`';
762
+ return result;
763
+ }
764
+
765
+ // ── Pipe: a |> f → f(a)
766
+ // a |> f(b, c) → f(a, b, c) ─────────────────────────
767
+ genPipe(node) {
768
+ const left = this.genExpr(node.left);
769
+ const right = node.right;
770
+
771
+ if (right.type === 'Identifier') {
772
+ const fn = this.genExpr(right);
773
+ return `${fn}(${left})`;
774
+ }
775
+ if (right.type === 'CallExpr') {
776
+ const fn = this.genExpr(right.callee);
777
+ const rest = right.args.map(a => this.genExpr(a)).join(', ');
778
+ return rest ? `${fn}(${left}, ${rest})` : `${fn}(${left})`;
779
+ }
780
+ // Lambda / other expression: treat as call
781
+ return `(${this.genExpr(right)})(${left})`;
782
+ }
783
+ }
784
+
785
+ module.exports = { CodeGenerator, CodeGenError };