@xnoxs/flux-lang 3.2.1 → 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/CHANGELOG.md +13 -0
- package/bin/flux.js +1 -1395
- package/dist/flux-cli.js +9564 -0
- package/dist/flux.cjs.js +1 -1
- package/dist/flux.esm.js +1 -1
- package/dist/flux.min.js +1 -1
- package/package.json +24 -16
- package/scripts/build.js +28 -29
- package/src/bundler.js +0 -216
- package/src/checker.js +0 -322
- package/src/codegen.js +0 -832
- package/src/css-preprocessor.js +0 -399
- package/src/jsx.js +0 -480
- package/src/lexer.js +0 -518
- package/src/linter.js +0 -784
- package/src/mangler.js +0 -280
- package/src/parser.js +0 -1708
- package/src/sourcemap.js +0 -82
- package/src/test-runner.js +0 -239
- package/src/transpiler.js +0 -172
- package/src/type-checker.js +0 -1206
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 };
|