@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,167 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Flux Self-Hosted Bundler
|
|
3
|
+
// src/self/bundler.flux — written in Flux, compiled by stage-0
|
|
4
|
+
//
|
|
5
|
+
// Bundles multiple Flux modules into a single JS file.
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
import Fs from "fs"
|
|
9
|
+
import Path from "path"
|
|
10
|
+
import { lexerize } from './lexer'
|
|
11
|
+
import { makeParser } from './parser'
|
|
12
|
+
import { makeCodeGen } from './codegen'
|
|
13
|
+
|
|
14
|
+
fn toModuleId(absPath):
|
|
15
|
+
val base = Path.basename(absPath, '.flux')
|
|
16
|
+
return '_flux_' + base.replace(/[^a-zA-Z0-9]/g, '_')
|
|
17
|
+
|
|
18
|
+
fn extractModuleInfo(ast, fromFile):
|
|
19
|
+
val imports = []
|
|
20
|
+
val exports = []
|
|
21
|
+
val body = []
|
|
22
|
+
val dir = Path.dirname(fromFile)
|
|
23
|
+
|
|
24
|
+
for node in ast.body:
|
|
25
|
+
if node.type == 'ImportDecl':
|
|
26
|
+
var src = node.source
|
|
27
|
+
if not src.endsWith('.flux'): src = src + '.flux'
|
|
28
|
+
val absPath = Path.resolve(dir, src)
|
|
29
|
+
imports.push({ names: node.names, source: node.source, absPath })
|
|
30
|
+
|
|
31
|
+
else if node.type == 'ExportDecl':
|
|
32
|
+
val inner = node.decl
|
|
33
|
+
if inner.type == 'FnDecl': exports.push(inner.name)
|
|
34
|
+
if inner.type == 'ClassDecl': exports.push(inner.name)
|
|
35
|
+
if inner.type == 'VarDecl': exports.push(inner.name)
|
|
36
|
+
body.push(inner)
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
body.push(node)
|
|
40
|
+
|
|
41
|
+
return { cleanAst: { type: 'Program', body }, imports, exports }
|
|
42
|
+
|
|
43
|
+
fn codegenModule(ast):
|
|
44
|
+
val cg = makeCodeGen({ indent: ' ' })
|
|
45
|
+
val result = cg.generate(ast)
|
|
46
|
+
val lines = result.code.split('\n')
|
|
47
|
+
val start = lines.findIndex(l -> l.trim() != '' and not l.includes('Generated by Flux') and not l.includes('"use strict"'))
|
|
48
|
+
return lines.slice(start).join('\n')
|
|
49
|
+
|
|
50
|
+
export class Bundler:
|
|
51
|
+
entryFile: string
|
|
52
|
+
options: any
|
|
53
|
+
modules: any
|
|
54
|
+
order: any[]
|
|
55
|
+
visited: any
|
|
56
|
+
inStack: any
|
|
57
|
+
|
|
58
|
+
fn bundle():
|
|
59
|
+
self.collect(self.entryFile)
|
|
60
|
+
return self.link()
|
|
61
|
+
|
|
62
|
+
fn collect(absPath):
|
|
63
|
+
if self.visited.has(absPath): return
|
|
64
|
+
if self.inStack.has(absPath):
|
|
65
|
+
throw new Error("[" + Path.basename(absPath) + "] Circular dependency detected: " + absPath)
|
|
66
|
+
if not Fs.existsSync(absPath):
|
|
67
|
+
throw new Error("[" + Path.basename(absPath) + "] File not found: " + absPath)
|
|
68
|
+
|
|
69
|
+
self.inStack.add(absPath)
|
|
70
|
+
|
|
71
|
+
val source = Fs.readFileSync(absPath, 'utf8')
|
|
72
|
+
|
|
73
|
+
var ast = null
|
|
74
|
+
try:
|
|
75
|
+
val tokens = lexerize(source).tokenize()
|
|
76
|
+
ast = makeParser(tokens).parse()
|
|
77
|
+
catch(e):
|
|
78
|
+
throw new Error("[" + Path.basename(absPath) + "] Parse error: " + e.message)
|
|
79
|
+
|
|
80
|
+
val info = extractModuleInfo(ast, absPath)
|
|
81
|
+
self.modules.set(absPath, { cleanAst: info.cleanAst, imports: info.imports, exports: info.exports, source, absPath })
|
|
82
|
+
|
|
83
|
+
for imp in info.imports:
|
|
84
|
+
self.collect(imp.absPath)
|
|
85
|
+
|
|
86
|
+
self.inStack.delete(absPath)
|
|
87
|
+
self.visited.add(absPath)
|
|
88
|
+
self.order.push(absPath)
|
|
89
|
+
|
|
90
|
+
fn link():
|
|
91
|
+
val lines = []
|
|
92
|
+
val banner = self.options.banner != false
|
|
93
|
+
|
|
94
|
+
if banner:
|
|
95
|
+
lines.push('// Generated by Flux Bundler (self-hosted)')
|
|
96
|
+
lines.push('// Entry: ' + Path.basename(self.entryFile))
|
|
97
|
+
lines.push('// Modules: ' + self.order.length)
|
|
98
|
+
lines.push('"use strict";')
|
|
99
|
+
lines.push('')
|
|
100
|
+
|
|
101
|
+
lines.push('(function() {')
|
|
102
|
+
lines.push('')
|
|
103
|
+
|
|
104
|
+
for absPath in self.order:
|
|
105
|
+
val mod = self.modules.get(absPath)
|
|
106
|
+
val isEntry = absPath == self.entryFile
|
|
107
|
+
val modId = toModuleId(absPath)
|
|
108
|
+
val relName = Path.relative(process.cwd(), absPath)
|
|
109
|
+
|
|
110
|
+
lines.push(' // ' + '─'.repeat(60))
|
|
111
|
+
lines.push(' // Module: ' + relName)
|
|
112
|
+
lines.push(' // ' + '─'.repeat(60))
|
|
113
|
+
|
|
114
|
+
if not isEntry:
|
|
115
|
+
lines.push(' var ' + modId + ' = (function() {')
|
|
116
|
+
lines.push(' var _exports = {};')
|
|
117
|
+
lines.push('')
|
|
118
|
+
|
|
119
|
+
for imp in mod.imports:
|
|
120
|
+
val srcId = toModuleId(imp.absPath)
|
|
121
|
+
if imp.names.length == 1:
|
|
122
|
+
lines.push(' var ' + imp.names[0] + ' = ' + srcId + ';')
|
|
123
|
+
else:
|
|
124
|
+
for name in imp.names:
|
|
125
|
+
lines.push(' var ' + name + ' = ' + srcId + '._exports ? ' + srcId + '._exports.' + name + ' : ' + srcId + '.' + name + ';')
|
|
126
|
+
|
|
127
|
+
if mod.imports.length > 0: lines.push('')
|
|
128
|
+
|
|
129
|
+
val moduleCode = codegenModule(mod.cleanAst)
|
|
130
|
+
lines.push(moduleCode)
|
|
131
|
+
|
|
132
|
+
if not isEntry:
|
|
133
|
+
if mod.exports.length > 0:
|
|
134
|
+
lines.push('')
|
|
135
|
+
for expName in mod.exports:
|
|
136
|
+
lines.push(' _exports.' + expName + ' = ' + expName + ';')
|
|
137
|
+
lines.push('')
|
|
138
|
+
lines.push(' return _exports;')
|
|
139
|
+
lines.push(' })()')
|
|
140
|
+
lines.push(' ' + modId + '._exports = ' + modId + ';')
|
|
141
|
+
|
|
142
|
+
lines.push('')
|
|
143
|
+
|
|
144
|
+
lines.push('})();')
|
|
145
|
+
return { code: lines.join('\n'), modules: self.order.length }
|
|
146
|
+
|
|
147
|
+
export fn makeBundler(entryFile, options):
|
|
148
|
+
return new Bundler(
|
|
149
|
+
Path.resolve(entryFile),
|
|
150
|
+
options ?? {},
|
|
151
|
+
new Map(),
|
|
152
|
+
[],
|
|
153
|
+
new Set(),
|
|
154
|
+
new Set()
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
export fn bundle(entryFile, options):
|
|
158
|
+
val result = { success: false, code: '', modules: 0, errors: [] }
|
|
159
|
+
try:
|
|
160
|
+
val b = makeBundler(entryFile, options)
|
|
161
|
+
val out = b.bundle()
|
|
162
|
+
result.code = out.code
|
|
163
|
+
result.modules = out.modules
|
|
164
|
+
result.success = true
|
|
165
|
+
catch(e):
|
|
166
|
+
result.errors.push({ message: e.message, name: e.name, file: null })
|
|
167
|
+
return result
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// ── Flux stdlib ──
|
|
2
|
+
|
|
3
|
+
function findIndex(arr, fn) { return arr.findIndex(fn); }
|
|
4
|
+
|
|
5
|
+
function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
|
|
6
|
+
|
|
7
|
+
function includes(arr, val) { return arr.includes(val); }
|
|
8
|
+
// ── end stdlib ──
|
|
9
|
+
|
|
10
|
+
// Generated by Flux Transpiler v3.1.0
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const Fs = require("fs");
|
|
14
|
+
const Path = require("path");
|
|
15
|
+
const { lexerize } = require("./lexer");
|
|
16
|
+
const { makeParser } = require("./parser");
|
|
17
|
+
const { makeCodeGen } = require("./codegen");
|
|
18
|
+
function toModuleId(absPath) {
|
|
19
|
+
const base = Path.basename(absPath, ".flux");
|
|
20
|
+
return ("_flux_" + base.replace(/[^a-zA-Z0-9]/g, "_"));
|
|
21
|
+
}
|
|
22
|
+
function extractModuleInfo(ast, fromFile) {
|
|
23
|
+
const imports = [];
|
|
24
|
+
const exports = [];
|
|
25
|
+
const body = [];
|
|
26
|
+
const dir = Path.dirname(fromFile);
|
|
27
|
+
for (const node of ast.body) {
|
|
28
|
+
if ((node.type == "ImportDecl")) {
|
|
29
|
+
let src = node.source;
|
|
30
|
+
if (!src.endsWith(".flux")) {
|
|
31
|
+
src = (src + ".flux");
|
|
32
|
+
}
|
|
33
|
+
const absPath = Path.resolve(dir, src);
|
|
34
|
+
imports.push({ names: node.names, source: node.source, absPath });
|
|
35
|
+
}
|
|
36
|
+
else if ((node.type == "ExportDecl")) {
|
|
37
|
+
const inner = node.decl;
|
|
38
|
+
if ((inner.type == "FnDecl")) {
|
|
39
|
+
exports.push(inner.name);
|
|
40
|
+
}
|
|
41
|
+
if ((inner.type == "ClassDecl")) {
|
|
42
|
+
exports.push(inner.name);
|
|
43
|
+
}
|
|
44
|
+
if ((inner.type == "VarDecl")) {
|
|
45
|
+
exports.push(inner.name);
|
|
46
|
+
}
|
|
47
|
+
body.push(inner);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
body.push(node);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { cleanAst: { type: "Program", body }, imports, exports };
|
|
54
|
+
}
|
|
55
|
+
function codegenModule(ast) {
|
|
56
|
+
const cg = makeCodeGen({ indent: " " });
|
|
57
|
+
const result = cg.generate(ast);
|
|
58
|
+
const lines = result.code.split("\n");
|
|
59
|
+
const start = lines.findIndex((l) => (((l.trim() != "") && !l.includes("Generated by Flux")) && !l.includes("\"use strict\"")));
|
|
60
|
+
return lines.slice(start).join("\n");
|
|
61
|
+
}
|
|
62
|
+
class Bundler {
|
|
63
|
+
constructor(entryFile, options, modules, order, visited, inStack) {
|
|
64
|
+
this.entryFile = entryFile;
|
|
65
|
+
this.options = options;
|
|
66
|
+
this.modules = modules;
|
|
67
|
+
this.order = order;
|
|
68
|
+
this.visited = visited;
|
|
69
|
+
this.inStack = inStack;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
bundle() {
|
|
73
|
+
this.collect(this.entryFile);
|
|
74
|
+
return this.link();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
collect(absPath) {
|
|
78
|
+
if (this.visited.has(absPath)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.inStack.has(absPath)) {
|
|
82
|
+
throw new Error(((("[" + Path.basename(absPath)) + "] Circular dependency detected: ") + absPath));
|
|
83
|
+
}
|
|
84
|
+
if (!Fs.existsSync(absPath)) {
|
|
85
|
+
throw new Error(((("[" + Path.basename(absPath)) + "] File not found: ") + absPath));
|
|
86
|
+
}
|
|
87
|
+
this.inStack.add(absPath);
|
|
88
|
+
const source = Fs.readFileSync(absPath, "utf8");
|
|
89
|
+
let ast = null;
|
|
90
|
+
try {
|
|
91
|
+
const tokens = lexerize(source).tokenize();
|
|
92
|
+
ast = makeParser(tokens).parse();
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
throw new Error(((("[" + Path.basename(absPath)) + "] Parse error: ") + e.message));
|
|
96
|
+
}
|
|
97
|
+
const info = extractModuleInfo(ast, absPath);
|
|
98
|
+
this.modules.set(absPath, { cleanAst: info.cleanAst, imports: info.imports, exports: info.exports, source, absPath });
|
|
99
|
+
for (const imp of info.imports) {
|
|
100
|
+
this.collect(imp.absPath);
|
|
101
|
+
}
|
|
102
|
+
this.inStack.delete(absPath);
|
|
103
|
+
this.visited.add(absPath);
|
|
104
|
+
this.order.push(absPath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
link() {
|
|
108
|
+
const lines = [];
|
|
109
|
+
const banner = (this.options.banner != false);
|
|
110
|
+
if (banner) {
|
|
111
|
+
lines.push("// Generated by Flux Bundler (self-hosted)");
|
|
112
|
+
lines.push(("// Entry: " + Path.basename(this.entryFile)));
|
|
113
|
+
lines.push(("// Modules: " + this.order.length));
|
|
114
|
+
lines.push("\"use strict\";");
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
lines.push("(function() {");
|
|
118
|
+
lines.push("");
|
|
119
|
+
for (const absPath of this.order) {
|
|
120
|
+
const mod = this.modules.get(absPath);
|
|
121
|
+
const isEntry = (absPath == this.entryFile);
|
|
122
|
+
const modId = toModuleId(absPath);
|
|
123
|
+
const relName = Path.relative(process.cwd(), absPath);
|
|
124
|
+
lines.push((" // " + "─".repeat(60)));
|
|
125
|
+
lines.push((" // Module: " + relName));
|
|
126
|
+
lines.push((" // " + "─".repeat(60)));
|
|
127
|
+
if (!isEntry) {
|
|
128
|
+
lines.push(((" var " + modId) + " = (function() {"));
|
|
129
|
+
lines.push(" var _exports = {};");
|
|
130
|
+
lines.push("");
|
|
131
|
+
}
|
|
132
|
+
for (const imp of mod.imports) {
|
|
133
|
+
const srcId = toModuleId(imp.absPath);
|
|
134
|
+
if ((imp.names.length == 1)) {
|
|
135
|
+
lines.push(((((" var " + imp.names[0]) + " = ") + srcId) + ";"));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
for (const name of imp.names) {
|
|
139
|
+
lines.push(((((((((((((" var " + name) + " = ") + srcId) + "._exports ? ") + srcId) + "._exports.") + name) + " : ") + srcId) + ".") + name) + ";"));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if ((mod.imports.length > 0)) {
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
const moduleCode = codegenModule(mod.cleanAst);
|
|
147
|
+
lines.push(moduleCode);
|
|
148
|
+
if (!isEntry) {
|
|
149
|
+
if ((mod.exports.length > 0)) {
|
|
150
|
+
lines.push("");
|
|
151
|
+
for (const expName of mod.exports) {
|
|
152
|
+
lines.push(((((" _exports." + expName) + " = ") + expName) + ";"));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
lines.push("");
|
|
156
|
+
lines.push(" return _exports;");
|
|
157
|
+
lines.push(" })()");
|
|
158
|
+
lines.push(((((" " + modId) + "._exports = ") + modId) + ";"));
|
|
159
|
+
}
|
|
160
|
+
lines.push("");
|
|
161
|
+
}
|
|
162
|
+
lines.push("})();");
|
|
163
|
+
return { code: lines.join("\n"), modules: this.order.length };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports.Bundler = Bundler;
|
|
169
|
+
function makeBundler(entryFile, options) {
|
|
170
|
+
return new Bundler(Path.resolve(entryFile), (options ?? { }), new Map(), [], new Set(), new Set());
|
|
171
|
+
}
|
|
172
|
+
module.exports.makeBundler = makeBundler;
|
|
173
|
+
function bundle(entryFile, options) {
|
|
174
|
+
const result = { success: false, code: "", modules: 0, errors: [] };
|
|
175
|
+
try {
|
|
176
|
+
const b = makeBundler(entryFile, options);
|
|
177
|
+
const out = b.bundle();
|
|
178
|
+
result.code = out.code;
|
|
179
|
+
result.modules = out.modules;
|
|
180
|
+
result.success = true;
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
result.errors.push({ message: e.message, name: e.name, file: null });
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
module.exports.bundle = bundle;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Flux Self-Hosted Static Checker
|
|
3
|
+
// src/self/checker.flux — written in Flux, compiled by stage-0
|
|
4
|
+
//
|
|
5
|
+
// Checks val immutability and basic scope correctness.
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
export class CheckError:
|
|
9
|
+
message: any
|
|
10
|
+
loc: any
|
|
11
|
+
hint: any
|
|
12
|
+
name: any
|
|
13
|
+
line: any
|
|
14
|
+
col: any
|
|
15
|
+
|
|
16
|
+
export class Scope:
|
|
17
|
+
parent: any
|
|
18
|
+
vars: any
|
|
19
|
+
|
|
20
|
+
fn define(name, kind, loc):
|
|
21
|
+
self.vars.set(name, { kind, loc })
|
|
22
|
+
|
|
23
|
+
fn lookup(name):
|
|
24
|
+
var scope = self
|
|
25
|
+
while scope:
|
|
26
|
+
if scope.vars.has(name): return scope.vars.get(name)
|
|
27
|
+
scope = scope.parent
|
|
28
|
+
return null
|
|
29
|
+
|
|
30
|
+
fn isVal(name):
|
|
31
|
+
val entry = self.lookup(name)
|
|
32
|
+
return entry and entry.kind == "val"
|
|
33
|
+
|
|
34
|
+
export class Checker:
|
|
35
|
+
errors: any[]
|
|
36
|
+
warnings: any[]
|
|
37
|
+
|
|
38
|
+
fn error(msg, loc, hint):
|
|
39
|
+
val err = new CheckError()
|
|
40
|
+
err.message = msg
|
|
41
|
+
err.loc = loc
|
|
42
|
+
err.hint = hint ?? null
|
|
43
|
+
err.name = "CheckError"
|
|
44
|
+
if loc:
|
|
45
|
+
err.line = loc.line
|
|
46
|
+
err.col = loc.col
|
|
47
|
+
self.errors.push(err)
|
|
48
|
+
|
|
49
|
+
fn warn(msg, loc, hint):
|
|
50
|
+
val w = new CheckError()
|
|
51
|
+
w.message = msg
|
|
52
|
+
w.loc = loc
|
|
53
|
+
w.hint = hint ?? null
|
|
54
|
+
w.name = "CheckError"
|
|
55
|
+
if loc:
|
|
56
|
+
w.line = loc.line
|
|
57
|
+
w.col = loc.col
|
|
58
|
+
self.warnings.push(w)
|
|
59
|
+
|
|
60
|
+
fn check(ast):
|
|
61
|
+
self.errors = []
|
|
62
|
+
self.warnings = []
|
|
63
|
+
val scope = new Scope(null, new Map())
|
|
64
|
+
self.walkStmts(ast.body, scope)
|
|
65
|
+
return { errors: self.errors, warnings: self.warnings }
|
|
66
|
+
|
|
67
|
+
fn walkStmts(stmts, scope):
|
|
68
|
+
for node in stmts: self.walkStmt(node, scope)
|
|
69
|
+
|
|
70
|
+
fn walkStmt(node, scope):
|
|
71
|
+
if not node: return
|
|
72
|
+
|
|
73
|
+
if node.type == "VarDecl":
|
|
74
|
+
if node.init: self.walkExpr(node.init, scope)
|
|
75
|
+
scope.define(node.name, node.kind, node.loc)
|
|
76
|
+
|
|
77
|
+
else if node.type == "DestructureDecl":
|
|
78
|
+
if node.init: self.walkExpr(node.init, scope)
|
|
79
|
+
if node.patternType == "object":
|
|
80
|
+
for p in node.pattern: scope.define(p.alias, node.kind, node.loc)
|
|
81
|
+
else:
|
|
82
|
+
for p in node.pattern:
|
|
83
|
+
if p: scope.define(p.name, node.kind, node.loc)
|
|
84
|
+
|
|
85
|
+
else if node.type == "FnDecl":
|
|
86
|
+
val inner = new Scope(scope, new Map())
|
|
87
|
+
for p in node.params: inner.define(p.name, "val", node.loc)
|
|
88
|
+
if node.name: scope.define(node.name, "val", node.loc)
|
|
89
|
+
if node.inline:
|
|
90
|
+
self.walkExpr(node.body, inner)
|
|
91
|
+
else:
|
|
92
|
+
self.walkStmts(node.body, inner)
|
|
93
|
+
|
|
94
|
+
else if node.type == "ClassDecl":
|
|
95
|
+
scope.define(node.name, "val", node.loc)
|
|
96
|
+
val cls = new Scope(scope, new Map())
|
|
97
|
+
for f in node.fields: cls.define(f.name, "var", node.loc)
|
|
98
|
+
for m in node.methods: self.walkStmt(m, cls)
|
|
99
|
+
|
|
100
|
+
else if node.type == "TypeDecl":
|
|
101
|
+
for v in node.variants: scope.define(v.name, "val", node.loc)
|
|
102
|
+
|
|
103
|
+
else if node.type == "InterfaceDecl":
|
|
104
|
+
scope.define(node.name, "val", node.loc)
|
|
105
|
+
|
|
106
|
+
else if node.type == "EnumDecl":
|
|
107
|
+
scope.define(node.name, "val", node.loc)
|
|
108
|
+
|
|
109
|
+
else if node.type == "IfStmt":
|
|
110
|
+
self.walkExpr(node.cond, scope)
|
|
111
|
+
self.walkStmts(node.then, new Scope(scope, new Map()))
|
|
112
|
+
for ei in node.elseifs:
|
|
113
|
+
self.walkExpr(ei.cond, scope)
|
|
114
|
+
self.walkStmts(ei.body, new Scope(scope, new Map()))
|
|
115
|
+
if node.else_: self.walkStmts(node.else_, new Scope(scope, new Map()))
|
|
116
|
+
|
|
117
|
+
else if node.type == "ForInStmt":
|
|
118
|
+
self.walkExpr(node.iter, scope)
|
|
119
|
+
val inner = new Scope(scope, new Map())
|
|
120
|
+
inner.define(node.var, "val", node.loc)
|
|
121
|
+
self.walkStmts(node.body, inner)
|
|
122
|
+
|
|
123
|
+
else if node.type == "WhileStmt":
|
|
124
|
+
self.walkExpr(node.cond, scope)
|
|
125
|
+
self.walkStmts(node.body, new Scope(scope, new Map()))
|
|
126
|
+
|
|
127
|
+
else if node.type == "DoWhileStmt":
|
|
128
|
+
self.walkStmts(node.body, new Scope(scope, new Map()))
|
|
129
|
+
self.walkExpr(node.cond, scope)
|
|
130
|
+
|
|
131
|
+
else if node.type == "MatchStmt":
|
|
132
|
+
self.walkExpr(node.subject, scope)
|
|
133
|
+
for arm in node.arms:
|
|
134
|
+
val inner = new Scope(scope, new Map())
|
|
135
|
+
if arm.pattern.type == "VariantPat":
|
|
136
|
+
for b in arm.pattern.bindings: inner.define(b, "val", node.loc)
|
|
137
|
+
if arm.guard: self.walkExpr(arm.guard, inner)
|
|
138
|
+
self.walkStmts(arm.body, inner)
|
|
139
|
+
|
|
140
|
+
else if node.type == "ReturnStmt":
|
|
141
|
+
if node.value: self.walkExpr(node.value, scope)
|
|
142
|
+
|
|
143
|
+
else if node.type == "ThrowStmt":
|
|
144
|
+
self.walkExpr(node.value, scope)
|
|
145
|
+
|
|
146
|
+
else if node.type == "TryCatchStmt":
|
|
147
|
+
self.walkStmts(node.tryBody, new Scope(scope, new Map()))
|
|
148
|
+
if node.catchBody:
|
|
149
|
+
val inner = new Scope(scope, new Map())
|
|
150
|
+
if node.catchParam: inner.define(node.catchParam, "val", node.loc)
|
|
151
|
+
self.walkStmts(node.catchBody, inner)
|
|
152
|
+
if node.finallyBody: self.walkStmts(node.finallyBody, new Scope(scope, new Map()))
|
|
153
|
+
|
|
154
|
+
else if node.type == "ImportDecl":
|
|
155
|
+
if node.defaultName: scope.define(node.defaultName, "val", node.loc)
|
|
156
|
+
if node.namespaceName: scope.define(node.namespaceName, "val", node.loc)
|
|
157
|
+
for n in node.names:
|
|
158
|
+
val nm = typeof n == "string" ? n : n.alias
|
|
159
|
+
scope.define(nm, "val", node.loc)
|
|
160
|
+
|
|
161
|
+
else if node.type == "ExportDecl":
|
|
162
|
+
if node.isDefault:
|
|
163
|
+
self.walkExpr(node.decl, scope)
|
|
164
|
+
else:
|
|
165
|
+
self.walkStmt(node.decl, scope)
|
|
166
|
+
|
|
167
|
+
else if node.type == "ExprStmt":
|
|
168
|
+
self.walkExpr(node.expr, scope)
|
|
169
|
+
|
|
170
|
+
fn walkExpr(node, scope):
|
|
171
|
+
if not node: return
|
|
172
|
+
|
|
173
|
+
if node.type == "AssignExpr":
|
|
174
|
+
val target = node.target
|
|
175
|
+
if target.type == "Identifier":
|
|
176
|
+
if scope.isVal(target.name):
|
|
177
|
+
self.error(
|
|
178
|
+
"Cannot reassign 'val " + target.name + "' — val is immutable",
|
|
179
|
+
target.loc,
|
|
180
|
+
"Use 'var " + target.name + "' if you need a mutable variable"
|
|
181
|
+
)
|
|
182
|
+
self.walkExpr(node.target, scope)
|
|
183
|
+
self.walkExpr(node.value, scope)
|
|
184
|
+
|
|
185
|
+
else if node.type == "UpdateExpr":
|
|
186
|
+
if node.operand.type == "Identifier" and scope.isVal(node.operand.name):
|
|
187
|
+
self.error(
|
|
188
|
+
"Cannot apply '" + node.op + "' to 'val " + node.operand.name + "' — val is immutable",
|
|
189
|
+
node.operand.loc,
|
|
190
|
+
"Use 'var " + node.operand.name + "' if you need a mutable variable"
|
|
191
|
+
)
|
|
192
|
+
self.walkExpr(node.operand, scope)
|
|
193
|
+
|
|
194
|
+
else if node.type == "BinaryExpr" or node.type == "PipeExpr":
|
|
195
|
+
self.walkExpr(node.left, scope)
|
|
196
|
+
self.walkExpr(node.right, scope)
|
|
197
|
+
|
|
198
|
+
else if node.type == "UnaryExpr" or node.type == "AwaitExpr" or node.type == "TypeofExpr":
|
|
199
|
+
self.walkExpr(node.operand, scope)
|
|
200
|
+
|
|
201
|
+
else if node.type == "TernaryExpr":
|
|
202
|
+
self.walkExpr(node.cond, scope)
|
|
203
|
+
self.walkExpr(node.then, scope)
|
|
204
|
+
self.walkExpr(node.else_, scope)
|
|
205
|
+
|
|
206
|
+
else if node.type == "CallExpr" or node.type == "OptCallExpr":
|
|
207
|
+
self.walkExpr(node.callee, scope)
|
|
208
|
+
for a in node.args: self.walkExpr(a, scope)
|
|
209
|
+
|
|
210
|
+
else if node.type == "MemberExpr" or node.type == "OptMemberExpr":
|
|
211
|
+
self.walkExpr(node.obj, scope)
|
|
212
|
+
|
|
213
|
+
else if node.type == "IndexExpr" or node.type == "OptIndexExpr":
|
|
214
|
+
self.walkExpr(node.obj, scope)
|
|
215
|
+
self.walkExpr(node.idx, scope)
|
|
216
|
+
|
|
217
|
+
else if node.type == "NewExpr":
|
|
218
|
+
for a in node.args: self.walkExpr(a, scope)
|
|
219
|
+
|
|
220
|
+
else if node.type == "ArrayExpr":
|
|
221
|
+
for item in node.items:
|
|
222
|
+
if item: self.walkExpr(item, scope)
|
|
223
|
+
|
|
224
|
+
else if node.type == "ObjectExpr":
|
|
225
|
+
for pair in node.pairs:
|
|
226
|
+
if pair.value: self.walkExpr(pair.value, scope)
|
|
227
|
+
|
|
228
|
+
else if node.type == "SpreadExpr":
|
|
229
|
+
self.walkExpr(node.expr, scope)
|
|
230
|
+
|
|
231
|
+
else if node.type == "LambdaExpr":
|
|
232
|
+
val inner = new Scope(scope, new Map())
|
|
233
|
+
for p in node.params: inner.define(p.name, "val", null)
|
|
234
|
+
self.walkExpr(node.body, inner)
|
|
235
|
+
|
|
236
|
+
else if node.type == "FnDecl":
|
|
237
|
+
val inner = new Scope(scope, new Map())
|
|
238
|
+
for p in node.params: inner.define(p.name, "val", null)
|
|
239
|
+
if node.inline:
|
|
240
|
+
self.walkExpr(node.body, inner)
|
|
241
|
+
else:
|
|
242
|
+
self.walkStmts(node.body, inner)
|
|
243
|
+
|
|
244
|
+
else if node.type == "CastExpr" or node.type == "AsConstExpr" or node.type == "SatisfiesExpr" or node.type == "IsExpr" or node.type == "NonNullExpr":
|
|
245
|
+
self.walkExpr(node.expr, scope)
|
|
246
|
+
|
|
247
|
+
else if node.type == "RangeExpr":
|
|
248
|
+
self.walkExpr(node.start, scope)
|
|
249
|
+
self.walkExpr(node.end, scope)
|