@xnoxs/flux-lang 3.1.1 → 3.2.0
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 +20 -0
- package/bin/flux.js +1 -1
- package/dist/flux.cjs.js +624 -77
- package/dist/flux.esm.js +624 -77
- package/dist/flux.min.js +260 -60
- package/package.json +1 -1
- package/src/codegen.js +56 -9
- package/src/linter.js +29 -3
- package/src/parser.js +41 -4
- package/src/stdlib.js +537 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnoxs/flux-lang",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
package/src/codegen.js
CHANGED
|
@@ -93,7 +93,7 @@ class CodeGenerator {
|
|
|
93
93
|
this.level = 0;
|
|
94
94
|
this._needsFmt = false;
|
|
95
95
|
|
|
96
|
-
this.emit('// Generated by Flux Transpiler v3.
|
|
96
|
+
this.emit('// Generated by Flux Transpiler v3.2.0');
|
|
97
97
|
this.emit('"use strict";');
|
|
98
98
|
this.blank();
|
|
99
99
|
|
|
@@ -189,6 +189,16 @@ class CodeGenerator {
|
|
|
189
189
|
this.out();
|
|
190
190
|
this.emit('}');
|
|
191
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
|
+
}
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
// ── class ──────────────────────────────────────────────────────
|
|
@@ -197,22 +207,47 @@ class CodeGenerator {
|
|
|
197
207
|
this.emit(`class ${node.name}${ext} {`);
|
|
198
208
|
this.in();
|
|
199
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
|
+
|
|
200
220
|
// Constructor
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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(', ');
|
|
204
232
|
this.emit(`constructor(${params}) {`);
|
|
205
233
|
this.in();
|
|
206
234
|
|
|
207
|
-
// super(...
|
|
235
|
+
// super(...parentParamFields) if extending
|
|
208
236
|
if (node.superClass && this.clsReg[node.superClass]) {
|
|
209
237
|
const parentFields = getAllFields(node.superClass, this.clsReg);
|
|
210
|
-
|
|
211
|
-
|
|
238
|
+
const parentParams = parentFields.filter(f => f.init == null);
|
|
239
|
+
if (parentParams.length > 0)
|
|
240
|
+
this.emit(`super(${parentParams.map(f => f.name).join(', ')});`);
|
|
212
241
|
}
|
|
213
242
|
|
|
214
|
-
// Assign own fields
|
|
215
|
-
for (const f of node.fields
|
|
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
|
+
|
|
216
251
|
this.out();
|
|
217
252
|
this.emit('}');
|
|
218
253
|
this.blank();
|
|
@@ -242,6 +277,18 @@ class CodeGenerator {
|
|
|
242
277
|
|
|
243
278
|
this.out();
|
|
244
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
|
+
|
|
245
292
|
this.blank();
|
|
246
293
|
}
|
|
247
294
|
|
package/src/linter.js
CHANGED
|
@@ -75,9 +75,35 @@ const BUILTIN_NAMES = new Set([
|
|
|
75
75
|
'WeakMap', 'WeakSet', 'Proxy', 'Reflect', 'undefined', 'null', 'true', 'false',
|
|
76
76
|
'NaN', 'Infinity', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'setTimeout',
|
|
77
77
|
'clearTimeout', 'setInterval', 'clearInterval', 'fetch', 'globalThis',
|
|
78
|
-
// Flux stdlib
|
|
79
|
-
'
|
|
80
|
-
|
|
78
|
+
// Flux stdlib — sequences
|
|
79
|
+
'range', 'zip', 'enumerate', 'flatten', 'chunk', 'unique', 'groupBy', 'sortBy',
|
|
80
|
+
// Flux stdlib — array pipe helpers
|
|
81
|
+
'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex',
|
|
82
|
+
'some', 'every', 'join', 'sort', 'flat', 'flatMap', 'includes',
|
|
83
|
+
// Flux stdlib — array extras
|
|
84
|
+
'first', 'last', 'take', 'drop', 'takeWhile', 'dropWhile',
|
|
85
|
+
'compact', 'intersection', 'difference', 'arrayUnion', 'unzip',
|
|
86
|
+
'countBy', 'minBy', 'maxBy', 'toPairs', 'partition', 'count',
|
|
87
|
+
'head', 'tail', 'nth', 'rotate', 'sliding',
|
|
88
|
+
// Flux stdlib — math
|
|
89
|
+
'clamp', 'sum', 'product', 'min', 'max', 'abs', 'floor', 'ceil', 'round',
|
|
90
|
+
'mean', 'median', 'stdDev', 'lerp', 'randInt', 'sample', 'shuffle',
|
|
91
|
+
// Flux stdlib — objects
|
|
92
|
+
'pick', 'omit', 'mapValues', 'filterValues', 'fromEntries',
|
|
93
|
+
'keys', 'values', 'entries', 'merge', 'invert', 'defaults',
|
|
94
|
+
'deepEqual', 'deepClone',
|
|
95
|
+
// Flux stdlib — strings
|
|
96
|
+
'capitalize', 'camelCase', 'snakeCase', 'kebabCase', 'truncate', 'pad',
|
|
97
|
+
'padStart', 'padEnd', 'trim', 'trimStart', 'trimEnd',
|
|
98
|
+
'words', 'lines', 'startsWith', 'endsWith', 'repeat', 'replaceAll', 'reverseStr',
|
|
99
|
+
// Flux stdlib — type checks
|
|
100
|
+
'isNil', 'isString', 'isNumber', 'isArray', 'isObject', 'isFunction', 'isBool',
|
|
101
|
+
// Flux stdlib — async
|
|
102
|
+
'sleep', 'retry', 'memoize', 'timeout', 'debounce', 'throttle', 'allSettled',
|
|
103
|
+
// Flux stdlib — functional
|
|
104
|
+
'pipe', 'compose', 'partial', 'curry', 'identity', 'noop', 'once', 'flip', 'complement',
|
|
105
|
+
// Flux builtins
|
|
106
|
+
'print',
|
|
81
107
|
]);
|
|
82
108
|
|
|
83
109
|
function isIgnoredName(name) {
|
package/src/parser.js
CHANGED
|
@@ -226,14 +226,38 @@ class Parser {
|
|
|
226
226
|
return stmts;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// ── Decorator parsing: @name or @name(args) ───────────────────
|
|
230
|
+
parseDecorators() {
|
|
231
|
+
const decorators = [];
|
|
232
|
+
while (this.check(T.AT)) {
|
|
233
|
+
const loc = this.skip(); // consume @
|
|
234
|
+
const name = this.eat(T.IDENT).value;
|
|
235
|
+
let args = [];
|
|
236
|
+
if (this.check(T.LPAREN)) {
|
|
237
|
+
this.pos++; // consume (
|
|
238
|
+
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
239
|
+
args.push(this.parseExpr());
|
|
240
|
+
if (!this.maybe(T.COMMA)) break;
|
|
241
|
+
}
|
|
242
|
+
this.eat(T.RPAREN);
|
|
243
|
+
}
|
|
244
|
+
decorators.push({ name, args, loc });
|
|
245
|
+
this.skipNewlines();
|
|
246
|
+
}
|
|
247
|
+
return decorators;
|
|
248
|
+
}
|
|
249
|
+
|
|
229
250
|
parseOneStmt() {
|
|
251
|
+
// Collect decorators (@decorator) before any declaration
|
|
252
|
+
const decorators = this.check(T.AT) ? this.parseDecorators() : [];
|
|
253
|
+
|
|
230
254
|
const tok = this.peek();
|
|
231
255
|
switch (tok.type) {
|
|
232
256
|
case T.VAR:
|
|
233
257
|
case T.VAL: return this.parseVarDecl();
|
|
234
|
-
case T.FN:
|
|
235
|
-
case T.ASYNC:
|
|
236
|
-
case T.CLASS:
|
|
258
|
+
case T.FN: { const n = this.parseFnDecl(); if (decorators.length) n.decorators = decorators; return n; }
|
|
259
|
+
case T.ASYNC: { const n = this.parseAsyncFn(); if (decorators.length) n.decorators = decorators; return n; }
|
|
260
|
+
case T.CLASS: { const n = this.parseClassDecl(); if (decorators.length) n.decorators = decorators; return n; }
|
|
237
261
|
case T.IF: return this.parseIf();
|
|
238
262
|
case T.FOR: return this.parseFor();
|
|
239
263
|
case T.WHILE: return this.parseWhile();
|
|
@@ -854,14 +878,27 @@ class Parser {
|
|
|
854
878
|
const m = this.check(T.ASYNC) ? this.parseAsyncFn() : this.parseFnDecl();
|
|
855
879
|
m.modifiers = mods; m.modifiers.add('static');
|
|
856
880
|
methods.push(m);
|
|
881
|
+
} else if (this.check(T.VAR) || this.check(T.VAL)) {
|
|
882
|
+
// `var fieldName: Type = init` or `var fieldName = init` in class body
|
|
883
|
+
const fieldKind = this.skip().type === T.VAR ? 'var' : 'val';
|
|
884
|
+
const fname = this.eat(T.IDENT).value;
|
|
885
|
+
let optional = false;
|
|
886
|
+
let ftype = null;
|
|
887
|
+
if (this.check(T.QUESTION)) { this.pos++; optional = true; }
|
|
888
|
+
if (this.check(T.COLON)) { this.pos++; ftype = this.parseTypeAnn(); }
|
|
889
|
+
let init = null;
|
|
890
|
+
if (this.check(T.EQ)) { this.pos++; init = this.parseExpr(); }
|
|
891
|
+
this.skipNewlines();
|
|
892
|
+
fields.push({ name: fname, typeAnn: ftype, optional, modifiers: mods, init, fieldKind });
|
|
857
893
|
} else if (this.check(T.IDENT)) {
|
|
894
|
+
// Bare `fieldName: Type` without var/val keyword (interface-style field in class)
|
|
858
895
|
const fname = this.eat(T.IDENT).value;
|
|
859
896
|
let optional = false;
|
|
860
897
|
if (this.check(T.QUESTION)) { this.pos++; optional = true; }
|
|
861
898
|
this.eat(T.COLON);
|
|
862
899
|
const ftype = this.parseTypeAnn();
|
|
863
900
|
this.skipNewlines();
|
|
864
|
-
fields.push({ name: fname, typeAnn: ftype, optional, modifiers: mods });
|
|
901
|
+
fields.push({ name: fname, typeAnn: ftype, optional, modifiers: mods, init: null });
|
|
865
902
|
} else {
|
|
866
903
|
this.skip();
|
|
867
904
|
}
|