@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnoxs/flux-lang",
3
- "version": "3.1.1",
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.1.0');
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
- const allFields = getAllFields(node.name, this.clsReg);
202
- if (allFields.length > 0) {
203
- const params = allFields.map(f => f.name).join(', ');
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(...parentFields) if extending
235
+ // super(...parentParamFields) if extending
208
236
  if (node.superClass && this.clsReg[node.superClass]) {
209
237
  const parentFields = getAllFields(node.superClass, this.clsReg);
210
- if (parentFields.length > 0)
211
- this.emit(`super(${parentFields.map(f => f.name).join(', ')});`);
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 only
215
- for (const f of node.fields) this.emit(`this.${f.name} = ${f.name};`);
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 names
79
- 'print', 'truncate', 'range', 'sum', 'first', 'last', 'zip', 'flatten',
80
- 'groupBy', 'unique', 'sortBy', 'chunk',
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: return this.parseFnDecl();
235
- case T.ASYNC: return this.parseAsyncFn();
236
- case T.CLASS: return this.parseClassDecl();
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
  }