@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/index.d.ts ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Flux Lang — TypeScript Definitions
3
+ * Transpiler API for embedding Flux compilation in TypeScript/JavaScript projects.
4
+ */
5
+
6
+ export interface TranspileError {
7
+ /** Human-readable error description */
8
+ message: string;
9
+ /** Error category name (e.g. "SyntaxError", "TypeError", "CodeGenError") */
10
+ name?: string;
11
+ /** Compilation stage where the error occurred */
12
+ stage?: 'css' | 'jsx' | 'lexer' | 'parser' | 'checker' | 'typecheck' | 'mangler' | 'codegen';
13
+ /** 1-based line number in the source file */
14
+ line?: number;
15
+ /** 1-based column number in the source file */
16
+ col?: number;
17
+ /** Optional hint for fixing the error */
18
+ hint?: string;
19
+ }
20
+
21
+ export interface TranspileWarning {
22
+ message: string;
23
+ line?: number;
24
+ col?: number;
25
+ hint?: string;
26
+ }
27
+
28
+ export interface TranspileOptions {
29
+ /**
30
+ * Generate an inline source map.
31
+ * The map URL is appended as `//# sourceMappingURL=<outputFile>.map`
32
+ * @default false
33
+ */
34
+ sourcemap?: boolean;
35
+
36
+ /**
37
+ * The name of the source `.flux` file — used in source map metadata.
38
+ * @default 'source.flux'
39
+ */
40
+ sourceFile?: string;
41
+
42
+ /**
43
+ * The name of the output `.js` file — used in source map metadata.
44
+ * @default 'output.js'
45
+ */
46
+ outputFile?: string;
47
+
48
+ /**
49
+ * Mangle (obfuscate) variable and function names in the output.
50
+ * @default false
51
+ */
52
+ mangle?: boolean;
53
+
54
+ /**
55
+ * Enable JSX transformation.
56
+ * @default true
57
+ */
58
+ jsx?: boolean;
59
+
60
+ /**
61
+ * JSX output target.
62
+ * - `'browser'` — uses `document.createElement` and `appendChild`
63
+ * - `'server'` — produces HTML strings
64
+ * @default 'browser'
65
+ */
66
+ jsxTarget?: 'browser' | 'server';
67
+
68
+ /**
69
+ * Run the static type checker and attach results to `typeErrors` / `typeWarnings`.
70
+ * @default true
71
+ */
72
+ typecheck?: boolean;
73
+
74
+ /**
75
+ * Run the val-immutability checker (ensures `val` bindings are not reassigned).
76
+ * @default false
77
+ */
78
+ check?: boolean;
79
+ }
80
+
81
+ export interface TranspileResult {
82
+ /** Whether compilation succeeded without errors */
83
+ success: boolean;
84
+
85
+ /** The compiled JavaScript code (empty string on failure) */
86
+ output: string;
87
+
88
+ /** Source map JSON string, or `null` if `sourcemap` option was not set */
89
+ sourceMap: string | null;
90
+
91
+ /** The Abstract Syntax Tree produced by the parser */
92
+ ast: object | null;
93
+
94
+ /** The token stream produced by the lexer */
95
+ tokens: object[] | null;
96
+
97
+ /** Syntax / code generation errors that caused compilation to fail */
98
+ errors: TranspileError[];
99
+
100
+ /** Type errors from the static type checker (does not halt compilation) */
101
+ typeErrors: TranspileError[];
102
+
103
+ /** Type warnings from the static type checker */
104
+ typeWarnings: TranspileWarning[];
105
+
106
+ /**
107
+ * The last stage that was executing when an error occurred, or `null` on success.
108
+ */
109
+ stage: 'css' | 'jsx' | 'lexer' | 'parser' | 'checker' | 'typecheck' | 'mangler' | 'codegen' | null;
110
+
111
+ /**
112
+ * Name mangling map — maps original names to mangled names.
113
+ * Only populated when `mangle: true`.
114
+ */
115
+ nameMap: Record<string, string> | null;
116
+ }
117
+
118
+ /**
119
+ * Transpile a Flux source string to JavaScript.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * import { transpile } from 'flux-lang';
124
+ *
125
+ * const result = transpile('val x = 42\nprint(x)');
126
+ * if (result.success) {
127
+ * console.log(result.output); // → "const x = 42;\nconsole.log(x);"
128
+ * } else {
129
+ * console.error(result.errors);
130
+ * }
131
+ * ```
132
+ */
133
+ export declare function transpile(source: string, options?: TranspileOptions): TranspileResult;
134
+
135
+
136
+ // ── Stdlib types ─────────────────────────────────────────────────────────────
137
+
138
+ /** Detect which Flux stdlib symbols are referenced in a JS code string. */
139
+ export declare function detectUsedSymbols(jsCode: string): string[];
140
+
141
+ /** Build the stdlib preamble JavaScript string. */
142
+ export declare function buildStdlib(symbols?: string[]): string;
143
+
144
+ /** All available Flux stdlib symbol names. */
145
+ export declare const STDLIB_SYMBOLS: readonly string[];
146
+
147
+
148
+ // ── Formatter ────────────────────────────────────────────────────────────────
149
+
150
+ /**
151
+ * Format Flux source code.
152
+ * Normalizes indentation, blank lines, and operator spacing.
153
+ */
154
+ export declare function format(source: string): string;
155
+
156
+
157
+ // ── Lexer ────────────────────────────────────────────────────────────────────
158
+
159
+ export interface Token {
160
+ type: string;
161
+ value: string | number | boolean | null;
162
+ line: number;
163
+ col: number;
164
+ }
165
+
166
+ export declare class Lexer {
167
+ constructor(source: string);
168
+ tokenize(): Token[];
169
+ }
170
+
171
+
172
+ // ── Parser ───────────────────────────────────────────────────────────────────
173
+
174
+ export interface AstNode {
175
+ type: string;
176
+ [key: string]: unknown;
177
+ }
178
+
179
+ export interface Program {
180
+ type: 'Program';
181
+ body: AstNode[];
182
+ }
183
+
184
+ export declare class Parser {
185
+ constructor(tokens: Token[]);
186
+ parse(): Program;
187
+ }
188
+
189
+
190
+ // ── Bundler ──────────────────────────────────────────────────────────────────
191
+
192
+ export interface BundleResult {
193
+ success: boolean;
194
+ code: string;
195
+ modules: number;
196
+ errors: TranspileError[];
197
+ }
198
+
199
+ /**
200
+ * Bundle a Flux entry file and all its imports into a single JavaScript file.
201
+ */
202
+ export declare function bundle(entryPath: string, options?: { minify?: boolean }): BundleResult;
package/index.js ADDED
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Flux Lang — Public API
5
+ *
6
+ * const { transpile } = require('flux-lang');
7
+ * import { transpile, format, buildStdlib } from 'flux-lang';
8
+ */
9
+
10
+ const { transpile } = require('./src/transpiler');
11
+ const { format } = require('./src/formatter');
12
+ const { buildStdlib, detectUsedSymbols, STDLIB_SYMBOLS } = require('./src/stdlib');
13
+ const { Lexer } = require('./src/lexer');
14
+ const { Parser } = require('./src/parser');
15
+ const { bundle } = require('./src/bundler');
16
+
17
+ module.exports = {
18
+ transpile,
19
+ format,
20
+ buildStdlib,
21
+ detectUsedSymbols,
22
+ STDLIB_SYMBOLS,
23
+ Lexer,
24
+ Parser,
25
+ bundle,
26
+ };
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@xnoxs/flux-lang",
3
+ "version": "3.1.1",
4
+ "description": "Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "bin": {
8
+ "flux": "./bin/flux.js"
9
+ },
10
+ "scripts": {
11
+ "build": "node scripts/build.js",
12
+ "prepublishOnly": "npm test && npm run build",
13
+ "test": "node bin/flux.js test tests/",
14
+ "test:file": "node bin/flux.js test",
15
+ "check": "node bin/flux.js check tests/01_basics.test.flux",
16
+ "bench": "node benchmarks/bench.js",
17
+ "start": "node bin/flux.js run app/server.flux",
18
+ "repl": "node bin/flux.js repl",
19
+ "version": "node bin/flux.js version"
20
+ },
21
+ "exports": {
22
+ ".": "./index.js",
23
+ "./stdlib": "./src/stdlib.js",
24
+ "./formatter":"./src/formatter.js",
25
+ "./dist/cjs": "./dist/flux.cjs.js",
26
+ "./dist/esm": "./dist/flux.esm.js"
27
+ },
28
+ "files": [
29
+ "bin/",
30
+ "src/",
31
+ "dist/",
32
+ "scripts/build.js",
33
+ "index.js",
34
+ "index.d.ts",
35
+ "README.md",
36
+ "CHANGELOG.md",
37
+ "LICENSE"
38
+ ],
39
+ "keywords": [
40
+ "transpiler",
41
+ "compiler",
42
+ "language",
43
+ "javascript",
44
+ "flux",
45
+ "flux-lang",
46
+ "async",
47
+ "functional",
48
+ "pattern-matching",
49
+ "type-safe",
50
+ "scripting",
51
+ "frontend",
52
+ "backend",
53
+ "jsx",
54
+ "algebraic-types"
55
+ ],
56
+ "author": "Flux Lang Contributors",
57
+ "license": "MIT",
58
+ "preferGlobal": true,
59
+ "engines": {
60
+ "node": ">=16.0.0"
61
+ },
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "https://github.com/flux-lang/flux-lang.git"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/flux-lang/flux-lang/issues"
68
+ },
69
+ "homepage": "https://flux-lang.dev",
70
+ "funding": {
71
+ "type": "github",
72
+ "url": "https://github.com/sponsors/flux-lang"
73
+ },
74
+ "devDependencies": {
75
+ "esbuild": "^0.28.1"
76
+ }
77
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Flux Lang — esbuild bundle script
6
+ *
7
+ * Produces:
8
+ * dist/flux.cjs.js — CommonJS bundle (Node, require())
9
+ * dist/flux.esm.js — ESM bundle (import / bundlers)
10
+ * dist/flux.min.js — Minified CJS bundle (browser / CDN)
11
+ *
12
+ * Usage:
13
+ * node scripts/build.js
14
+ */
15
+
16
+ const esbuild = require('esbuild');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
21
+ const banner = `/*!
22
+ * flux-lang v${pkg.version}
23
+ * ${pkg.description}
24
+ * (c) ${new Date().getFullYear()} Flux Lang Contributors
25
+ * Released under the ${pkg.license} License
26
+ */`;
27
+
28
+ const outDir = path.resolve(__dirname, '../dist');
29
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
30
+
31
+ const shared = {
32
+ entryPoints: ['index.js'],
33
+ bundle: true,
34
+ platform: 'node',
35
+ target: ['node16'],
36
+ external: ['esbuild'], // esbuild stays dev-only
37
+ banner: { js: banner },
38
+ };
39
+
40
+ async function build() {
41
+ const start = Date.now();
42
+
43
+ await Promise.all([
44
+ // CJS bundle (for Node require())
45
+ esbuild.build({
46
+ ...shared,
47
+ format: 'cjs',
48
+ outfile: 'dist/flux.cjs.js',
49
+ }),
50
+
51
+ // ESM bundle (for modern bundlers / type:"module" projects)
52
+ esbuild.build({
53
+ ...shared,
54
+ format: 'esm',
55
+ outfile: 'dist/flux.esm.js',
56
+ }),
57
+
58
+ // Minified CJS (CDN / browser via require shim)
59
+ esbuild.build({
60
+ ...shared,
61
+ format: 'cjs',
62
+ minify: true,
63
+ outfile: 'dist/flux.min.js',
64
+ }),
65
+ ]);
66
+
67
+ const sizes = ['dist/flux.cjs.js', 'dist/flux.esm.js', 'dist/flux.min.js'].map(f => {
68
+ const bytes = fs.statSync(f).size;
69
+ const kb = (bytes / 1024).toFixed(1);
70
+ return ` ${f.padEnd(22)} ${kb} kB`;
71
+ });
72
+
73
+ console.log(`\nBuild complete in ${Date.now() - start}ms\n${sizes.join('\n')}\n`);
74
+ }
75
+
76
+ build().catch(e => { console.error(e); process.exit(1); });
package/src/bundler.js ADDED
@@ -0,0 +1,216 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { Lexer } = require('./lexer');
6
+ const { Parser } = require('./parser');
7
+ const { CodeGenerator } = require('./codegen');
8
+
9
+ class BundleError extends Error {
10
+ constructor(msg, file) {
11
+ super(file ? `[${path.basename(file)}] ${msg}` : msg);
12
+ this.name = 'BundleError';
13
+ this.file = file;
14
+ }
15
+ }
16
+
17
+ // ── AST transformation helpers ────────────────────────────────────────────────
18
+
19
+ // Hapus node ImportDeclaration dan ExportDeclaration dari body AST
20
+ // Kembalikan: { cleanAst, imports, exports }
21
+ function extractModuleInfo(ast, fromFile) {
22
+ const imports = []; // { names: [], source, absPath }
23
+ const exports = []; // nama yang diekspor
24
+ const body = [];
25
+ const dir = path.dirname(fromFile);
26
+
27
+ for (const node of ast.body) {
28
+ if (node.type === 'ImportDecl') {
29
+ let src = node.source;
30
+ if (!src.endsWith('.flux')) src += '.flux';
31
+ const absPath = path.resolve(dir, src);
32
+ imports.push({ names: node.names, source: node.source, absPath });
33
+
34
+ } else if (node.type === 'ExportDecl') {
35
+ const inner = node.decl;
36
+ if (inner.type === 'FnDecl') exports.push(inner.name);
37
+ if (inner.type === 'ClassDecl') exports.push(inner.name);
38
+ if (inner.type === 'VarDecl') exports.push(inner.name);
39
+ body.push(inner);
40
+
41
+ } else {
42
+ body.push(node);
43
+ }
44
+ }
45
+
46
+ return { cleanAst: { type: 'Program', body }, imports, exports };
47
+ }
48
+
49
+ // Hasilkan kode JS dari clean AST
50
+ function codegenModule(ast) {
51
+ const { code } = new CodeGenerator({ indent: ' ' }).generate(ast);
52
+ // Hapus header boilerplate dari tiap modul (dua baris pertama)
53
+ const lines = code.split('\n');
54
+ // Hapus "// Dihasilkan oleh Flux Transpiler", '"use strict";', dan baris kosong pertama
55
+ const start = lines.findIndex(l => l.trim() !== '' && !l.includes('Dihasilkan') && !l.includes('"use strict"'));
56
+ return lines.slice(start).join('\n');
57
+ }
58
+
59
+ // Konversi nama modul (path) ke identifier JS yang aman
60
+ function toModuleId(absPath) {
61
+ const base = path.basename(absPath, '.flux');
62
+ return '_flux_' + base.replace(/[^a-zA-Z0-9]/g, '_');
63
+ }
64
+
65
+ // ── Bundler class ─────────────────────────────────────────────────────────────
66
+
67
+ class Bundler {
68
+ constructor(entryFile, options = {}) {
69
+ this.entryFile = path.resolve(entryFile);
70
+ this.options = options;
71
+ this.modules = new Map(); // absPath → { ast, imports, exports, source }
72
+ this.order = []; // topological order (dependencies first)
73
+ this.visited = new Set();
74
+ this.inStack = new Set(); // cycle detection
75
+ }
76
+
77
+ bundle() {
78
+ this.collect(this.entryFile);
79
+ return this.link();
80
+ }
81
+
82
+ // ── Fase 1: Kumpulkan semua modul secara rekursif ─────────────────
83
+ collect(absPath) {
84
+ if (this.visited.has(absPath)) return;
85
+ if (this.inStack.has(absPath)) {
86
+ throw new BundleError(`Dependensi sirkular terdeteksi: ${absPath}`, absPath);
87
+ }
88
+ if (!fs.existsSync(absPath)) {
89
+ throw new BundleError(`File tidak ditemukan: ${absPath}`, absPath);
90
+ }
91
+
92
+ this.inStack.add(absPath);
93
+
94
+ const source = fs.readFileSync(absPath, 'utf8');
95
+
96
+ // Lex + Parse
97
+ let ast;
98
+ try {
99
+ const tokens = new Lexer(source).tokenize();
100
+ ast = new Parser(tokens).parse();
101
+ } catch (e) {
102
+ throw new BundleError(`Error saat parsing: ${e.message}`, absPath);
103
+ }
104
+
105
+ const { cleanAst, imports, exports } = extractModuleInfo(ast, absPath);
106
+ this.modules.set(absPath, { cleanAst, imports, exports, source, absPath });
107
+
108
+ // Proses dependensi dulu (DFS)
109
+ for (const imp of imports) {
110
+ this.collect(imp.absPath);
111
+ }
112
+
113
+ this.inStack.delete(absPath);
114
+ this.visited.add(absPath);
115
+ this.order.push(absPath); // entry ditambahkan terakhir
116
+ }
117
+
118
+ // ── Fase 2: Link semua modul menjadi satu file JS ─────────────────
119
+ link() {
120
+ const lines = [];
121
+ const banner = this.options.banner !== false;
122
+
123
+ if (banner) {
124
+ lines.push('// Dihasilkan oleh Flux Bundler');
125
+ lines.push(`// Entry: ${path.basename(this.entryFile)}`);
126
+ lines.push(`// Modul: ${this.order.length}`);
127
+ lines.push('"use strict";');
128
+ lines.push('');
129
+ }
130
+
131
+ lines.push('(function() {');
132
+ lines.push('');
133
+
134
+ for (const absPath of this.order) {
135
+ const { cleanAst, imports, exports } = this.modules.get(absPath);
136
+ const isEntry = absPath === this.entryFile;
137
+ const modId = toModuleId(absPath);
138
+ const relName = path.relative(process.cwd(), absPath);
139
+
140
+ lines.push(` // ${'─'.repeat(60)}`);
141
+ lines.push(` // Modul: ${relName}`);
142
+ lines.push(` // ${'─'.repeat(60)}`);
143
+
144
+ if (!isEntry) {
145
+ // Bungkus modul non-entry dalam object exports
146
+ lines.push(` var ${modId} = (function() {`);
147
+ lines.push(` var _exports = {};`);
148
+ lines.push('');
149
+ }
150
+
151
+ // Inject import bindings dari modul lain
152
+ for (const imp of imports) {
153
+ const srcId = toModuleId(imp.absPath);
154
+ if (imp.names.length === 1) {
155
+ // import defaultExport from "./mod"
156
+ lines.push(` var ${imp.names[0]} = ${srcId};`);
157
+ } else {
158
+ // import { a, b } from "./mod"
159
+ for (const name of imp.names) {
160
+ lines.push(` var ${name} = ${srcId}._exports ? ${srcId}._exports.${name} : ${srcId}.${name};`);
161
+ }
162
+ }
163
+ }
164
+
165
+ if (imports.length > 0) lines.push('');
166
+
167
+ // Kode modul yang sudah dibersihkan
168
+ const moduleCode = codegenModule(cleanAst);
169
+ lines.push(moduleCode);
170
+
171
+ if (!isEntry) {
172
+ // Registrasikan semua export ke object _exports
173
+ if (exports.length > 0) {
174
+ lines.push('');
175
+ for (const expName of exports) {
176
+ lines.push(` _exports.${expName} = ${expName};`);
177
+ }
178
+ }
179
+ lines.push('');
180
+ lines.push(` return _exports;`);
181
+ lines.push(` })()`);
182
+ lines.push(` ${modId}._exports = ${modId};`);
183
+ }
184
+
185
+ lines.push('');
186
+ }
187
+
188
+ lines.push('})();');
189
+
190
+ return { code: lines.join('\n'), modules: this.order.length };
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Bundle beberapa file Flux menjadi satu JS.
196
+ * @param {string} entryFile - path ke file .flux utama
197
+ * @param {object} options
198
+ * @returns {{ success, code, modules, errors }}
199
+ */
200
+ function bundle(entryFile, options = {}) {
201
+ const result = { success: false, code: '', modules: 0, errors: [] };
202
+
203
+ try {
204
+ const bundler = new Bundler(entryFile, options);
205
+ const { code, modules } = bundler.bundle();
206
+ result.code = code;
207
+ result.modules = modules;
208
+ result.success = true;
209
+ } catch (e) {
210
+ result.errors.push({ message: e.message, name: e.name, file: e.file || null });
211
+ }
212
+
213
+ return result;
214
+ }
215
+
216
+ module.exports = { bundle, Bundler, BundleError };