@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.
- package/CHANGELOG.md +103 -0
- package/README.md +1089 -0
- package/bin/flux.js +1397 -0
- package/dist/flux.cjs.js +6664 -0
- package/dist/flux.esm.js +6674 -0
- package/dist/flux.min.js +263 -0
- package/index.d.ts +202 -0
- package/index.js +26 -0
- package/package.json +77 -0
- package/scripts/build.js +76 -0
- package/src/bundler.js +216 -0
- package/src/checker.js +322 -0
- package/src/codegen.js +785 -0
- package/src/css-preprocessor.js +399 -0
- package/src/formatter.js +140 -0
- package/src/jsx.js +480 -0
- package/src/lexer.js +518 -0
- package/src/linter.js +758 -0
- package/src/mangler.js +280 -0
- package/src/parser.js +1671 -0
- package/src/self/bundler.flux +167 -0
- package/src/self/bundler.js +187 -0
- package/src/self/checker.flux +249 -0
- package/src/self/checker.js +338 -0
- package/src/self/codegen.flux +555 -0
- package/src/self/codegen.js +784 -0
- package/src/self/css-preprocessor.flux +373 -0
- package/src/self/css-preprocessor.js +387 -0
- package/src/self/formatter.flux +93 -0
- package/src/self/formatter.js +114 -0
- package/src/self/jsx.flux +430 -0
- package/src/self/jsx.js +396 -0
- package/src/self/lexer.flux +529 -0
- package/src/self/lexer.js +709 -0
- package/src/self/lexer.stage2.js +700 -0
- package/src/self/linter.flux +515 -0
- package/src/self/linter.js +804 -0
- package/src/self/mangler.flux +253 -0
- package/src/self/mangler.js +348 -0
- package/src/self/parser.flux +1146 -0
- package/src/self/parser.js +1571 -0
- package/src/self/sourcemap.flux +66 -0
- package/src/self/sourcemap.js +72 -0
- package/src/self/stdlib.flux +356 -0
- package/src/self/stdlib.js +396 -0
- package/src/self/test-runner.flux +201 -0
- package/src/self/test-runner.js +132 -0
- package/src/self/transpiler.flux +123 -0
- package/src/self/transpiler.js +83 -0
- package/src/self/type-checker.flux +821 -0
- package/src/self/type-checker.js +1106 -0
- package/src/sourcemap.js +82 -0
- package/src/stdlib.js +436 -0
- package/src/test-runner.js +239 -0
- package/src/transpiler.js +172 -0
- package/src/type-checker.js +1206 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// ── Flux stdlib ──
|
|
2
|
+
|
|
3
|
+
function map(arr, fn) { return arr.map(fn); }
|
|
4
|
+
|
|
5
|
+
function filter(arr, fn) { return arr.filter(fn); }
|
|
6
|
+
|
|
7
|
+
function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
|
|
8
|
+
|
|
9
|
+
function sort(arr, fn) { return arr.slice().sort(fn); }
|
|
10
|
+
// ── end stdlib ──
|
|
11
|
+
|
|
12
|
+
// Generated by Flux Transpiler v3.1.0
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
const Fs = require("fs");
|
|
16
|
+
const Path = require("path");
|
|
17
|
+
const Os = require("os");
|
|
18
|
+
const C = { reset: "\\x1b[0m", bold: "\\x1b[1m", red: "\\x1b[31m", green: "\\x1b[32m", yellow: "\\x1b[33m", cyan: "\\x1b[36m", gray: "\\x1b[90m", dim: "\\x1b[2m" };
|
|
19
|
+
function clr(c, s) {
|
|
20
|
+
return (process.env.NO_COLOR ? s : ((c + s) + C.reset));
|
|
21
|
+
}
|
|
22
|
+
const TEST_HELPERS = "\"use strict\";\n\nconst __results = [];\nlet __current = null;\n\nfunction assert(condition, message) {\n if (!condition) {\n throw new Error(message || 'Assertion failed: expected truthy');\n }\n}\n\nfunction assertEqual(actual, expected, message) {\n const ok = JSON.stringify(actual) === JSON.stringify(expected);\n if (!ok) {\n throw new Error(\n message ||\n 'Expected ' + JSON.stringify(expected) + ' but got ' + JSON.stringify(actual)\n );\n }\n}\n\nfunction assertNotEqual(actual, expected, message) {\n const ok = JSON.stringify(actual) !== JSON.stringify(expected);\n if (!ok) {\n throw new Error(message || 'Expected values to be different but both were ' + JSON.stringify(actual));\n }\n}\n\nfunction assertThrows(fn, message) {\n try { fn(); }\n catch (_) { return; }\n throw new Error(message || 'Expected function to throw, but it did not');\n}\n\nfunction assertClose(actual, expected, delta, message) {\n delta = delta || 1e-9;\n if (Math.abs(actual - expected) > delta) {\n throw new Error(message || ('Expected ' + actual + ' to be close to ' + expected + ' (delta=' + delta + ')'));\n }\n}\n\nfunction __runTest(name, fn) {\n try {\n fn();\n __results.push({ name: name, ok: true });\n } catch(e) {\n __results.push({ name: name, ok: false, error: e.message });\n }\n}";
|
|
23
|
+
function discoverTestFiles(target) {
|
|
24
|
+
const abs = Path.resolve(target);
|
|
25
|
+
if (!Fs.existsSync(abs)) {
|
|
26
|
+
throw new Error(("Not found: " + abs));
|
|
27
|
+
}
|
|
28
|
+
const stat = Fs.statSync(abs);
|
|
29
|
+
if (stat.isDirectory()) {
|
|
30
|
+
return Fs.readdirSync(abs).filter((f) => f.endsWith(".test.flux")).map((f) => Path.join(abs, f)).sort();
|
|
31
|
+
}
|
|
32
|
+
if (abs.endsWith(".flux")) {
|
|
33
|
+
return [abs];
|
|
34
|
+
}
|
|
35
|
+
throw new Error(("Expected a .flux file or directory, got: " + abs));
|
|
36
|
+
}
|
|
37
|
+
module.exports.discoverTestFiles = discoverTestFiles;
|
|
38
|
+
function runTestFile(filePath, transpile) {
|
|
39
|
+
const source = Fs.readFileSync(filePath, "utf8");
|
|
40
|
+
const result = transpile(source);
|
|
41
|
+
if (!result.success) {
|
|
42
|
+
return { file: Path.basename(filePath), errors: result.errors, tests: [], compile: false };
|
|
43
|
+
}
|
|
44
|
+
const fnNames = [];
|
|
45
|
+
const fnRe = /^function (test_[a-zA-Z0-9_]+)\s*\(/mg;
|
|
46
|
+
let m = fnRe.exec(result.output);
|
|
47
|
+
while (m) {
|
|
48
|
+
fnNames.push(m[1]);
|
|
49
|
+
m = fnRe.exec(result.output);
|
|
50
|
+
}
|
|
51
|
+
const runnerParts = [TEST_HELPERS, result.output, "", "// ── auto-generated test runner ──"];
|
|
52
|
+
for (const n of fnNames) {
|
|
53
|
+
runnerParts.push((((("__runTest(" + JSON.stringify(n.replace(/^test_/, "").replace(/_/g, " "))) + ", ") + n) + ");"));
|
|
54
|
+
}
|
|
55
|
+
runnerParts.push("module.exports = { __results };");
|
|
56
|
+
const runner = runnerParts.join("\n");
|
|
57
|
+
const tmpPath = Path.join(Os.tmpdir(), (("_flux_test_" + Date.now()) + ".js"));
|
|
58
|
+
try {
|
|
59
|
+
Fs.writeFileSync(tmpPath, runner, "utf8");
|
|
60
|
+
const mod = require(tmpPath);
|
|
61
|
+
const tests = ((mod && mod.__results) ? mod.__results : []);
|
|
62
|
+
return { file: Path.basename(filePath), tests, compile: true, errors: [] };
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return { file: Path.basename(filePath), tests: [], compile: false, errors: [{ message: ("Runtime error: " + e.message) }] };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
try {
|
|
69
|
+
Fs.unlinkSync(tmpPath);
|
|
70
|
+
}
|
|
71
|
+
catch (ignored) {
|
|
72
|
+
null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
module.exports.runTestFile = runTestFile;
|
|
77
|
+
function runTests(target, transpile) {
|
|
78
|
+
let files = [];
|
|
79
|
+
try {
|
|
80
|
+
files = discoverTestFiles(target);
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
console.error(clr(C.red, ("✗ " + e.message)));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if ((files.length == 0)) {
|
|
87
|
+
console.log(clr(C.yellow, ("No *.test.flux files found in: " + target)));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let totalPass = 0;
|
|
91
|
+
let totalFail = 0;
|
|
92
|
+
let totalFiles = 0;
|
|
93
|
+
const t0 = Date.now();
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
totalFiles = (totalFiles + 1);
|
|
96
|
+
console.log(clr(C.cyan, ("\n◈ " + Path.basename(file))));
|
|
97
|
+
const result = runTestFile(file, transpile);
|
|
98
|
+
if (!result.compile) {
|
|
99
|
+
console.log(clr(C.red, " ✗ Compile error:"));
|
|
100
|
+
for (const e of result.errors) {
|
|
101
|
+
console.log(clr(C.red, (" " + e.message)));
|
|
102
|
+
}
|
|
103
|
+
totalFail = (totalFail + 1);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if ((result.tests.length == 0)) {
|
|
107
|
+
console.log(clr(C.yellow, " (no test_ functions found)"));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const t of result.tests) {
|
|
111
|
+
if (t.ok) {
|
|
112
|
+
totalPass = (totalPass + 1);
|
|
113
|
+
console.log((clr(C.green, " ✓") + clr(C.gray, (" " + t.name))));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
totalFail = (totalFail + 1);
|
|
117
|
+
console.log(clr(C.red, (" ✗ " + t.name)));
|
|
118
|
+
console.log(clr(C.dim, (" " + t.error)));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const elapsed = (Date.now() - t0);
|
|
123
|
+
const total = (totalPass + totalFail);
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(clr(C.bold, "─".repeat(50)));
|
|
126
|
+
console.log(((((clr(C.bold, "Results: ") + clr(C.green, (totalPass + " passed"))) + clr(C.gray, ", ")) + ((totalFail > 0) ? clr(C.red, (totalFail + " failed")) : clr(C.gray, (totalFail + " failed")))) + clr(C.gray, ((((" (" + total) + " total) in ") + elapsed) + "ms"))));
|
|
127
|
+
console.log();
|
|
128
|
+
if ((totalFail > 0)) {
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
module.exports.runTests = runTests;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Flux Self-Hosted Transpiler — v3.0 (100% Self-Hosting)
|
|
3
|
+
// src/self/transpiler.flux — written in Flux, compiled by stage-0
|
|
4
|
+
//
|
|
5
|
+
// Full pipeline:
|
|
6
|
+
// CSS-preprocess → JSX-preprocess → Lex → Parse →
|
|
7
|
+
// Immutability-check → Type-check → Codegen
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
import { Lexer, lexerize, T } from './lexer'
|
|
11
|
+
import { Parser, makeParser } from './parser'
|
|
12
|
+
import { CodeGenerator, makeCodeGen } from './codegen'
|
|
13
|
+
import { transformCss } from './css-preprocessor'
|
|
14
|
+
import { transformJsx, FLUX_H_BROWSER, FLUX_H_SERVER, FLUX_CSS_BROWSER, FLUX_CSS_SERVER } from './jsx'
|
|
15
|
+
import { Checker } from './checker'
|
|
16
|
+
import { FluxTypeChecker } from './type-checker'
|
|
17
|
+
|
|
18
|
+
// ── Version info ─────────────────────────────────────────────
|
|
19
|
+
export val FLUX_VERSION = "3.0.0"
|
|
20
|
+
export val FLUX_STAGE = "self-hosted"
|
|
21
|
+
|
|
22
|
+
// ── Public API ────────────────────────────────────────────────
|
|
23
|
+
export fn transpile(source, options):
|
|
24
|
+
val opts = options ?? {}
|
|
25
|
+
|
|
26
|
+
val result = {
|
|
27
|
+
success: false,
|
|
28
|
+
output: "",
|
|
29
|
+
ast: null,
|
|
30
|
+
tokens: null,
|
|
31
|
+
errors: [],
|
|
32
|
+
typeErrors: [],
|
|
33
|
+
typeWarnings: [],
|
|
34
|
+
stage: "",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
var src = source
|
|
39
|
+
|
|
40
|
+
// ── Stage 0a: CSS preprocessing ────────────────────────────
|
|
41
|
+
result.stage = "css"
|
|
42
|
+
if opts.css != false:
|
|
43
|
+
src = transformCss(src)
|
|
44
|
+
|
|
45
|
+
// ── Stage 0b: JSX preprocessing ────────────────────────────
|
|
46
|
+
result.stage = "jsx"
|
|
47
|
+
if opts.jsx:
|
|
48
|
+
val jsxTarget = opts.jsxTarget ?? "browser"
|
|
49
|
+
val jsxResult = transformJsx(src, { target: jsxTarget })
|
|
50
|
+
src = jsxResult.source
|
|
51
|
+
// JSX runtime helpers will be prepended to output if JSX detected
|
|
52
|
+
if jsxResult.hasJsx:
|
|
53
|
+
opts._jsxHelpers = jsxResult.runtimeHelpers
|
|
54
|
+
|
|
55
|
+
// ── Stage 1: Lex ────────────────────────────────────────────
|
|
56
|
+
result.stage = "lexer"
|
|
57
|
+
val lexer = lexerize(src)
|
|
58
|
+
val tokens = lexer.tokenize()
|
|
59
|
+
result.tokens = tokens
|
|
60
|
+
|
|
61
|
+
// ── Stage 2: Parse ──────────────────────────────────────────
|
|
62
|
+
result.stage = "parser"
|
|
63
|
+
val ast = makeParser(tokens).parse()
|
|
64
|
+
result.ast = ast
|
|
65
|
+
|
|
66
|
+
// ── Stage 3: Immutability check ─────────────────────────────
|
|
67
|
+
if opts.check != false:
|
|
68
|
+
result.stage = "checker"
|
|
69
|
+
val checker = new Checker([], [])
|
|
70
|
+
val checkResult = checker.check(ast)
|
|
71
|
+
if checkResult.errors.length > 0:
|
|
72
|
+
result.errors = checkResult.errors
|
|
73
|
+
result.stage = null
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
// ── Stage 4: Type checking ──────────────────────────────────
|
|
77
|
+
if opts.typecheck:
|
|
78
|
+
result.stage = "type-checker"
|
|
79
|
+
val tc = new FluxTypeChecker([], [], new Map(), new Map(), new Map(), new Map())
|
|
80
|
+
val tcResult = tc.check(ast)
|
|
81
|
+
result.typeErrors = tcResult.errors
|
|
82
|
+
result.typeWarnings = tcResult.warnings
|
|
83
|
+
// Type errors are non-fatal warnings unless strict mode
|
|
84
|
+
if opts.strict and tcResult.errors.length > 0:
|
|
85
|
+
result.errors = tcResult.errors
|
|
86
|
+
result.stage = null
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
// ── Stage 5: Code generation ────────────────────────────────
|
|
90
|
+
result.stage = "codegen"
|
|
91
|
+
val indent = opts.mangle ? "" : " "
|
|
92
|
+
val cg = makeCodeGen({ indent })
|
|
93
|
+
val genResult = cg.generate(ast)
|
|
94
|
+
var code = genResult.code
|
|
95
|
+
|
|
96
|
+
// Prepend JSX runtime helpers if needed
|
|
97
|
+
if opts._jsxHelpers:
|
|
98
|
+
code = opts._jsxHelpers + "\n" + code
|
|
99
|
+
|
|
100
|
+
result.output = code
|
|
101
|
+
result.success = true
|
|
102
|
+
result.stage = null
|
|
103
|
+
|
|
104
|
+
catch(e):
|
|
105
|
+
result.errors.push({
|
|
106
|
+
message: e.message,
|
|
107
|
+
name: e.name,
|
|
108
|
+
stage: result.stage,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
// ── Convenience: transpile with full options ──────────────────
|
|
114
|
+
export fn transpileFile(source, opts):
|
|
115
|
+
return transpile(source, {
|
|
116
|
+
check: true,
|
|
117
|
+
typecheck: opts?.typecheck ?? false,
|
|
118
|
+
jsx: opts?.jsx ?? false,
|
|
119
|
+
jsxTarget: opts?.jsxTarget ?? "browser",
|
|
120
|
+
mangle: opts?.mangle ?? false,
|
|
121
|
+
strict: opts?.strict ?? false,
|
|
122
|
+
...(opts ?? {}),
|
|
123
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Generated by Flux Transpiler v3.1.0
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { Lexer, lexerize, T } = require("./lexer");
|
|
5
|
+
const { Parser, makeParser } = require("./parser");
|
|
6
|
+
const { CodeGenerator, makeCodeGen } = require("./codegen");
|
|
7
|
+
const { transformCss } = require("./css-preprocessor");
|
|
8
|
+
const { transformJsx, FLUX_H_BROWSER, FLUX_H_SERVER, FLUX_CSS_BROWSER, FLUX_CSS_SERVER } = require("./jsx");
|
|
9
|
+
const { Checker } = require("./checker");
|
|
10
|
+
const { FluxTypeChecker } = require("./type-checker");
|
|
11
|
+
const FLUX_VERSION = "3.0.0";
|
|
12
|
+
module.exports.FLUX_VERSION = FLUX_VERSION;
|
|
13
|
+
const FLUX_STAGE = "self-hosted";
|
|
14
|
+
module.exports.FLUX_STAGE = FLUX_STAGE;
|
|
15
|
+
function transpile(source, options) {
|
|
16
|
+
const opts = (options ?? { });
|
|
17
|
+
const result = { success: false, output: "", ast: null, tokens: null, errors: [], typeErrors: [], typeWarnings: [], stage: "" };
|
|
18
|
+
try {
|
|
19
|
+
let src = source;
|
|
20
|
+
result.stage = "css";
|
|
21
|
+
if ((opts.css != false)) {
|
|
22
|
+
src = transformCss(src);
|
|
23
|
+
}
|
|
24
|
+
result.stage = "jsx";
|
|
25
|
+
if (opts.jsx) {
|
|
26
|
+
const jsxTarget = (opts.jsxTarget ?? "browser");
|
|
27
|
+
const jsxResult = transformJsx(src, { target: jsxTarget });
|
|
28
|
+
src = jsxResult.source;
|
|
29
|
+
if (jsxResult.hasJsx) {
|
|
30
|
+
opts._jsxHelpers = jsxResult.runtimeHelpers;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
result.stage = "lexer";
|
|
34
|
+
const lexer = lexerize(src);
|
|
35
|
+
const tokens = lexer.tokenize();
|
|
36
|
+
result.tokens = tokens;
|
|
37
|
+
result.stage = "parser";
|
|
38
|
+
const ast = makeParser(tokens).parse();
|
|
39
|
+
result.ast = ast;
|
|
40
|
+
if ((opts.check != false)) {
|
|
41
|
+
result.stage = "checker";
|
|
42
|
+
const checker = new Checker([], []);
|
|
43
|
+
const checkResult = checker.check(ast);
|
|
44
|
+
if ((checkResult.errors.length > 0)) {
|
|
45
|
+
result.errors = checkResult.errors;
|
|
46
|
+
result.stage = null;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (opts.typecheck) {
|
|
51
|
+
result.stage = "type-checker";
|
|
52
|
+
const tc = new FluxTypeChecker([], [], new Map(), new Map(), new Map(), new Map());
|
|
53
|
+
const tcResult = tc.check(ast);
|
|
54
|
+
result.typeErrors = tcResult.errors;
|
|
55
|
+
result.typeWarnings = tcResult.warnings;
|
|
56
|
+
if ((opts.strict && (tcResult.errors.length > 0))) {
|
|
57
|
+
result.errors = tcResult.errors;
|
|
58
|
+
result.stage = null;
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
result.stage = "codegen";
|
|
63
|
+
const indent = (opts.mangle ? "" : " ");
|
|
64
|
+
const cg = makeCodeGen({ indent });
|
|
65
|
+
const genResult = cg.generate(ast);
|
|
66
|
+
let code = genResult.code;
|
|
67
|
+
if (opts._jsxHelpers) {
|
|
68
|
+
code = ((opts._jsxHelpers + "\n") + code);
|
|
69
|
+
}
|
|
70
|
+
result.output = code;
|
|
71
|
+
result.success = true;
|
|
72
|
+
result.stage = null;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
result.errors.push({ message: e.message, name: e.name, stage: result.stage });
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
module.exports.transpile = transpile;
|
|
80
|
+
function transpileFile(source, opts) {
|
|
81
|
+
return transpile(source, { check: true, typecheck: (opts?.typecheck ?? false), jsx: (opts?.jsx ?? false), jsxTarget: (opts?.jsxTarget ?? "browser"), mangle: (opts?.mangle ?? false), strict: (opts?.strict ?? false), ...(opts ?? { }) });
|
|
82
|
+
}
|
|
83
|
+
module.exports.transpileFile = transpileFile;
|