@xnoxs/flux-lang 3.1.2 → 3.2.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.
- package/CHANGELOG.md +27 -0
- package/README.md +915 -597
- package/dist/flux.cjs.js +594 -75
- package/dist/flux.esm.js +594 -75
- package/dist/flux.min.js +258 -58
- package/package.json +1 -1
- package/src/codegen.js +41 -9
- package/src/linter.js +29 -3
- package/src/parser.js +27 -3
- 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.1
|
|
3
|
+
"version": "3.2.1",
|
|
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,16 +207,26 @@ 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
221
|
// Fields split into:
|
|
202
222
|
// paramFields — no default init (become constructor params)
|
|
203
223
|
// initFields — have a default init expression (assigned in body, not params)
|
|
204
|
-
const allFields
|
|
205
|
-
const paramFields = allFields.filter(f => f.init == null);
|
|
224
|
+
const allFields = getAllFields(node.name, this.clsReg);
|
|
225
|
+
const paramFields = allFields.filter(f => f.init == null && !isPrivate(f));
|
|
206
226
|
const initFields = allFields.filter(f => f.init != null);
|
|
207
227
|
const ownInitFields = node.fields.filter(f => f.init != null);
|
|
208
228
|
|
|
209
|
-
const needsCtor = paramFields.length > 0 || initFields.length > 0;
|
|
229
|
+
const needsCtor = paramFields.length > 0 || initFields.length > 0 || privateFields.length > 0;
|
|
210
230
|
if (needsCtor) {
|
|
211
231
|
const params = paramFields.map(f => f.name).join(', ');
|
|
212
232
|
this.emit(`constructor(${params}) {`);
|
|
@@ -220,13 +240,13 @@ class CodeGenerator {
|
|
|
220
240
|
this.emit(`super(${parentParams.map(f => f.name).join(', ')});`);
|
|
221
241
|
}
|
|
222
242
|
|
|
223
|
-
// Assign own param-fields
|
|
224
|
-
for (const f of node.fields.filter(f => f.init == null))
|
|
225
|
-
this.emit(
|
|
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};`);
|
|
226
246
|
|
|
227
|
-
// Assign own init-fields
|
|
247
|
+
// Assign own init-fields (including private ones with defaults)
|
|
228
248
|
for (const f of ownInitFields)
|
|
229
|
-
this.emit(
|
|
249
|
+
this.emit(`${fieldRef(f)} = ${this.genExpr(f.init)};`);
|
|
230
250
|
|
|
231
251
|
this.out();
|
|
232
252
|
this.emit('}');
|
|
@@ -257,6 +277,18 @@ class CodeGenerator {
|
|
|
257
277
|
|
|
258
278
|
this.out();
|
|
259
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
|
+
|
|
260
292
|
this.blank();
|
|
261
293
|
}
|
|
262
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();
|