@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/README.md
CHANGED
|
@@ -1,74 +1,164 @@
|
|
|
1
|
-
# Flux Lang v3.
|
|
1
|
+
# ⚡ Flux Lang v3.2.0
|
|
2
2
|
|
|
3
|
-
**Flux**
|
|
3
|
+
**Flux** is a modern programming language that transpiles to clean JavaScript — combining Python-style indentation, TypeScript-grade type safety, Rust-inspired pattern matching, and a rich standard library — all with **zero runtime overhead**.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
source.flux → [Lexer → Parser → TypeChecker → CodeGen] → output.js
|
|
6
|
+
source.flux → [CSS Pre → JSX Pre → Lexer → Parser → TypeChecker → CodeGen] → output.js
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
Every Flux file compiles to plain `.js` — no virtual machine, no custom runtime. You get the full JavaScript ecosystem for free.
|
|
10
|
+
|
|
9
11
|
---
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Installation
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
+
# Install globally
|
|
17
|
+
npm install -g @xnoxs/flux-lang
|
|
16
18
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
+
# Or locally in a project
|
|
20
|
+
npm install --save-dev @xnoxs/flux-lang
|
|
21
|
+
```
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
node bin/flux.js check app.flux
|
|
23
|
+
**Node.js API** — use `require('@xnoxs/flux-lang')` (or `require('@xnoxs/flux-lang/src/transpiler')`) to transpile Flux programmatically:
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
```js
|
|
26
|
+
const { transpile } = require('@xnoxs/flux-lang');
|
|
27
|
+
const result = transpile('val x = 42\nprint(x)', {});
|
|
28
|
+
console.log(result.output);
|
|
29
|
+
```
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
node bin/flux.js test tests/
|
|
31
|
+
---
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
## CLI Reference
|
|
34
|
+
|
|
35
|
+
| Command | Description |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `flux run file.flux` | Compile and immediately execute |
|
|
38
|
+
| `flux compile file.flux` | Compile to JavaScript |
|
|
39
|
+
| `flux compile file.flux -o out.js` | Compile to specific output file |
|
|
40
|
+
| `flux bundle main.flux -o bundle.js` | Bundle all imports into one file |
|
|
41
|
+
| `flux watch file.flux` | Watch for changes and auto-compile |
|
|
42
|
+
| `flux check file.flux` | Type check + static analysis |
|
|
43
|
+
| `flux lint file.flux` | Lint — unused vars, unreachable code, shadowing |
|
|
44
|
+
| `flux fmt file.flux` | Format source code |
|
|
45
|
+
| `flux test [dir]` | Find and run all `*.test.flux` files |
|
|
46
|
+
| `flux tokens file.flux` | Show lexer token stream |
|
|
47
|
+
| `flux ast file.flux` | Show AST as JSON |
|
|
48
|
+
| `flux repl` | Interactive REPL |
|
|
49
|
+
| `flux version` | Show version |
|
|
50
|
+
| `flux init [name]` | Create new Flux project |
|
|
51
|
+
|
|
52
|
+
### Flags
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
```
|
|
55
|
+
--out, -o <file> Output file
|
|
56
|
+
--sourcemap, -m Generate source map (.js.map)
|
|
57
|
+
--stdout Print output to terminal
|
|
58
|
+
--no-color Disable color output
|
|
34
59
|
```
|
|
35
60
|
|
|
36
61
|
---
|
|
37
62
|
|
|
38
|
-
##
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```flux
|
|
66
|
+
// Immutable and mutable variables
|
|
67
|
+
val name = "Flux"
|
|
68
|
+
var count = 0
|
|
69
|
+
|
|
70
|
+
// Typed function
|
|
71
|
+
fn greet(who: String) -> String:
|
|
72
|
+
return "Hello, {who}!"
|
|
73
|
+
|
|
74
|
+
print(greet(name)) // Hello, Flux!
|
|
75
|
+
|
|
76
|
+
// Arrays + pipe operator
|
|
77
|
+
val result = [1, 2, 3, 4, 5]
|
|
78
|
+
|> filter(n -> n % 2 == 0)
|
|
79
|
+
|> map(n -> n * n)
|
|
80
|
+
|> sum // 20
|
|
39
81
|
|
|
40
|
-
|
|
82
|
+
// Algebraic Data Types + pattern matching
|
|
83
|
+
type Result = Ok(value) | Err(message)
|
|
84
|
+
|
|
85
|
+
fn divide(a, b):
|
|
86
|
+
if b == 0: return Err("division by zero")
|
|
87
|
+
return Ok(a / b)
|
|
88
|
+
|
|
89
|
+
match divide(10, 2):
|
|
90
|
+
when Ok(v) -> print("Result: {v}")
|
|
91
|
+
when Err(e) -> print("Error: {e}")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Variables & Data Types
|
|
41
97
|
|
|
42
98
|
```flux
|
|
43
99
|
val x = 42 // immutable (const)
|
|
44
100
|
var count = 0 // mutable (let)
|
|
45
101
|
|
|
46
|
-
//
|
|
102
|
+
// With type annotation
|
|
47
103
|
val name: String = "Flux"
|
|
48
|
-
val age:
|
|
49
|
-
val
|
|
50
|
-
val
|
|
51
|
-
val nothing: Null = null
|
|
104
|
+
val age: Int = 3
|
|
105
|
+
val pi: Float = 3.14159
|
|
106
|
+
val on: Bool = true
|
|
52
107
|
|
|
53
|
-
//
|
|
54
|
-
val items = [1, 2, 3]
|
|
55
|
-
val greeting = "Hello"
|
|
108
|
+
// Type is inferred automatically when omitted
|
|
109
|
+
val items = [1, 2, 3] // inferred: Array<Int>
|
|
110
|
+
val greeting = "Hello" // inferred: String
|
|
56
111
|
```
|
|
57
112
|
|
|
58
|
-
###
|
|
113
|
+
### Primitive Types
|
|
114
|
+
|
|
115
|
+
| Type | Description | Example |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `String` | Text | `"Hello"`, `'World'` |
|
|
118
|
+
| `Int` | Integer | `42`, `-7` |
|
|
119
|
+
| `Float` | Decimal | `3.14`, `-0.5` |
|
|
120
|
+
| `Number` | Int or Float | `100`, `3.14` |
|
|
121
|
+
| `Bool` | True or false | `true`, `false` |
|
|
122
|
+
| `Null` | Null value | `null` |
|
|
123
|
+
| `Void` | No return value | empty return |
|
|
124
|
+
| `Never` | Never completes | throw / infinite loop |
|
|
125
|
+
| `Any` | Skip type check | all types |
|
|
126
|
+
| `Unknown` | Needs narrowing first | — |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Strings & Regex
|
|
131
|
+
|
|
132
|
+
Flux has four string literal types:
|
|
59
133
|
|
|
60
134
|
```flux
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
print("Pi
|
|
64
|
-
print("
|
|
65
|
-
print("
|
|
135
|
+
// Double-quoted — interpolation + format specs
|
|
136
|
+
val n = 3.14159
|
|
137
|
+
print("Pi is {n:.2f}") // Pi is 3.14
|
|
138
|
+
print("Big: {5000000:,}") // Big: 5,000,000
|
|
139
|
+
print("Rate: {0.1573:.1%}") // Rate: 15.7%
|
|
140
|
+
|
|
141
|
+
// Single-quoted — no interpolation (safe for CSS/HTML)
|
|
142
|
+
val css = 'color: #38bdf8'
|
|
143
|
+
|
|
144
|
+
// Backtick — multiline, no interpolation
|
|
145
|
+
val html = `
|
|
146
|
+
<div class="card">
|
|
147
|
+
<h2>Hello</h2>
|
|
148
|
+
</div>
|
|
149
|
+
`
|
|
150
|
+
|
|
151
|
+
// Regex literals
|
|
152
|
+
val emailPattern = /^[a-z]+@[a-z]+\.[a-z]+$/i
|
|
153
|
+
val isEmail = emailPattern.test("user@example.com") // true
|
|
66
154
|
```
|
|
67
155
|
|
|
68
|
-
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Operators
|
|
69
159
|
|
|
70
160
|
```flux
|
|
71
|
-
//
|
|
161
|
+
// Arithmetic
|
|
72
162
|
val a = 10 + 3 // 13
|
|
73
163
|
val b = 10 - 3 // 7
|
|
74
164
|
val c = 10 * 3 // 30
|
|
@@ -76,12 +166,12 @@ val d = 10 / 3 // 3.333...
|
|
|
76
166
|
val e = 10 % 3 // 1
|
|
77
167
|
val f = 2 ** 10 // 1024
|
|
78
168
|
|
|
79
|
-
//
|
|
169
|
+
// Comparison
|
|
80
170
|
val eq = a == b // false
|
|
81
171
|
val neq = a != b // true
|
|
82
172
|
val lt = a < b // false
|
|
83
173
|
|
|
84
|
-
//
|
|
174
|
+
// Logical
|
|
85
175
|
val and_ = true && false // false
|
|
86
176
|
val or_ = true || false // true
|
|
87
177
|
val not_ = !true // false
|
|
@@ -91,16 +181,32 @@ val result = maybeNull ?? "default"
|
|
|
91
181
|
|
|
92
182
|
// Optional chaining
|
|
93
183
|
val len = user?.name?.length
|
|
184
|
+
|
|
185
|
+
// Range (exclusive)
|
|
186
|
+
for i in 0..10: print(i) // 0 to 9
|
|
187
|
+
|
|
188
|
+
// Pipe operator
|
|
189
|
+
val total = nums |> filter(n -> n > 0) |> sum
|
|
190
|
+
|
|
191
|
+
// Spread
|
|
192
|
+
val merged = { ...defaults, ...overrides }
|
|
193
|
+
|
|
194
|
+
// Non-null assertion
|
|
195
|
+
val upper = value!.toUpperCase()
|
|
196
|
+
|
|
197
|
+
// Cast and satisfies
|
|
198
|
+
val n = expr as Int
|
|
199
|
+
val config = obj satisfies Config
|
|
94
200
|
```
|
|
95
201
|
|
|
96
202
|
---
|
|
97
203
|
|
|
98
|
-
##
|
|
204
|
+
## Functions
|
|
99
205
|
|
|
100
|
-
###
|
|
206
|
+
### Block & Inline
|
|
101
207
|
|
|
102
208
|
```flux
|
|
103
|
-
// Inline
|
|
209
|
+
// Inline — single expression
|
|
104
210
|
fn double(x) -> x * 2
|
|
105
211
|
fn add(a, b) -> a + b
|
|
106
212
|
fn greet(name) -> "Hello, {name}!"
|
|
@@ -114,134 +220,167 @@ fn factorial(n):
|
|
|
114
220
|
### Type Annotations
|
|
115
221
|
|
|
116
222
|
```flux
|
|
117
|
-
// Parameter dan return type
|
|
118
223
|
fn greet(name: String) -> String:
|
|
119
224
|
return "Hello, {name}!"
|
|
120
225
|
|
|
121
|
-
fn add(a: Int, b: Int) -> Int:
|
|
122
|
-
return a + b
|
|
123
|
-
|
|
124
226
|
fn area(w: Float, h: Float) -> Float:
|
|
125
227
|
return w * h
|
|
126
228
|
|
|
127
|
-
//
|
|
229
|
+
// Optional param (caller may omit it)
|
|
128
230
|
fn greetUser(name: String, title?: String) -> String:
|
|
129
231
|
val prefix = title ?? ""
|
|
130
|
-
if prefix != "":
|
|
131
|
-
return "{prefix} {name}"
|
|
232
|
+
if prefix != "": return "{prefix} {name}"
|
|
132
233
|
return name
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Sintaks Return Type
|
|
136
|
-
|
|
137
|
-
Gunakan `-> ReturnType:` sebelum badan blok fungsi:
|
|
138
|
-
|
|
139
|
-
```flux
|
|
140
|
-
// Blok body dengan return type annotation
|
|
141
|
-
fn multiply(a: Int, b: Int) -> Int:
|
|
142
|
-
return a * b
|
|
143
234
|
|
|
144
|
-
//
|
|
145
|
-
fn square(x) -> x * x
|
|
146
|
-
|
|
147
|
-
// Nullable return type
|
|
148
|
-
fn findUser(id: Int) -> String?:
|
|
149
|
-
if id > 0: return "user_{id}"
|
|
150
|
-
return null
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Tipe Return Khusus
|
|
154
|
-
|
|
155
|
-
```flux
|
|
156
|
-
// Void — tidak mengembalikan nilai
|
|
235
|
+
// Void / Never
|
|
157
236
|
fn logMessage(msg: String) -> Void:
|
|
158
237
|
print(msg)
|
|
159
238
|
|
|
160
|
-
// Never — selalu melempar error (tidak pernah selesai)
|
|
161
239
|
fn crash(msg: String) -> Never:
|
|
162
240
|
throw new Error(msg)
|
|
163
241
|
```
|
|
164
242
|
|
|
165
|
-
###
|
|
243
|
+
### Default & Rest Parameters
|
|
166
244
|
|
|
167
245
|
```flux
|
|
168
|
-
// Rest parameter
|
|
169
|
-
fn sum(...nums):
|
|
170
|
-
return nums.reduce((acc, x) -> acc + x, 0)
|
|
171
|
-
|
|
172
|
-
// Default value
|
|
173
246
|
fn repeat(str: String, times: Int = 3) -> String:
|
|
174
247
|
return str.repeat(times)
|
|
248
|
+
|
|
249
|
+
fn sum(...nums):
|
|
250
|
+
return nums.reduce((acc, x) -> acc + x, 0)
|
|
175
251
|
```
|
|
176
252
|
|
|
177
|
-
###
|
|
253
|
+
### Async Functions
|
|
178
254
|
|
|
179
255
|
```flux
|
|
180
256
|
async fn fetchData(url: String) -> String:
|
|
181
|
-
val res
|
|
182
|
-
|
|
257
|
+
val res = await fetch(url)
|
|
258
|
+
val text = await res.text()
|
|
259
|
+
return text
|
|
183
260
|
|
|
184
261
|
async fn main():
|
|
185
|
-
val data = await fetchData("https://api.example.com")
|
|
262
|
+
val data = await fetchData("https://api.example.com/data")
|
|
186
263
|
print(data)
|
|
187
264
|
```
|
|
188
265
|
|
|
189
|
-
### Higher-Order
|
|
266
|
+
### Lambda & Higher-Order
|
|
190
267
|
|
|
191
268
|
```flux
|
|
192
|
-
// Lambda ekspresi
|
|
193
269
|
val square = x -> x * x
|
|
194
|
-
val add
|
|
270
|
+
val add = (a, b) -> a + b
|
|
271
|
+
|
|
272
|
+
// Multi-statement lambda
|
|
273
|
+
val process = x -> {
|
|
274
|
+
val doubled = x * 2
|
|
275
|
+
return doubled + 1
|
|
276
|
+
}
|
|
195
277
|
|
|
196
|
-
//
|
|
278
|
+
// Functions as arguments
|
|
197
279
|
val doubled = [1, 2, 3].map(x -> x * 2) // [2, 4, 6]
|
|
198
|
-
val evens = [1
|
|
199
|
-
val total = [1, 2, 3, 4].reduce((acc, x) -> acc + x, 0) // 10
|
|
280
|
+
val evens = [1..10].filter(x -> x % 2 == 0) // [2, 4, 6, 8, 10]
|
|
200
281
|
|
|
201
|
-
// Pipe operator —
|
|
282
|
+
// Pipe operator — chain transformations
|
|
202
283
|
val result = [1, 2, 3, 4, 5]
|
|
203
284
|
|> filter(x -> x > 2)
|
|
204
285
|
|> map(x -> x * 10)
|
|
205
|
-
|> sum
|
|
286
|
+
|> sum // 120
|
|
206
287
|
```
|
|
207
288
|
|
|
208
289
|
---
|
|
209
290
|
|
|
210
|
-
##
|
|
291
|
+
## Control Flow
|
|
211
292
|
|
|
212
|
-
|
|
293
|
+
### If / Elif / Else
|
|
213
294
|
|
|
214
|
-
|
|
295
|
+
```flux
|
|
296
|
+
if x > 0:
|
|
297
|
+
print("positive")
|
|
298
|
+
elif x < 0:
|
|
299
|
+
print("negative")
|
|
300
|
+
else:
|
|
301
|
+
print("zero")
|
|
215
302
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
| `Int` | Bilangan bulat | `42`, `-7` |
|
|
220
|
-
| `Float` | Bilangan desimal | `3.14`, `-0.5` |
|
|
221
|
-
| `Number` | Int atau Float | `100`, `3.14` |
|
|
222
|
-
| `Bool` | Benar atau salah | `true`, `false` |
|
|
223
|
-
| `Null` | Nilai null | `null` |
|
|
224
|
-
| `Void` | Tidak mengembalikan nilai | return kosong |
|
|
225
|
-
| `Never` | Tidak pernah selesai | throw / infinite loop |
|
|
226
|
-
| `Any` | Lewati type check | semua tipe |
|
|
227
|
-
| `Unknown` | Perlu narrowing dulu | — |
|
|
303
|
+
// Ternary
|
|
304
|
+
val label = x > 0 ? "positive" : "non-positive"
|
|
305
|
+
```
|
|
228
306
|
|
|
229
|
-
###
|
|
307
|
+
### For / While / Do-While
|
|
230
308
|
|
|
231
309
|
```flux
|
|
232
|
-
|
|
233
|
-
|
|
310
|
+
// Iterate array
|
|
311
|
+
for item in items:
|
|
312
|
+
print(item)
|
|
234
313
|
|
|
235
|
-
|
|
236
|
-
|
|
314
|
+
// Range
|
|
315
|
+
for i in 0..10:
|
|
316
|
+
print(i) // 0 to 9
|
|
317
|
+
|
|
318
|
+
// for..of — any JS iterable
|
|
319
|
+
for char of "hello":
|
|
320
|
+
print(char)
|
|
321
|
+
|
|
322
|
+
// For with array destructuring
|
|
323
|
+
for [i, v] in enumerate(items):
|
|
324
|
+
print("{i}: {v}")
|
|
325
|
+
|
|
326
|
+
// Labeled break / continue
|
|
327
|
+
outer: for i in 0..5:
|
|
328
|
+
for j in 0..5:
|
|
329
|
+
if j == 2: break outer
|
|
330
|
+
|
|
331
|
+
// While
|
|
332
|
+
var n = 10
|
|
333
|
+
while n > 0:
|
|
334
|
+
print(n)
|
|
335
|
+
n -= 1
|
|
336
|
+
|
|
337
|
+
// Do-while
|
|
338
|
+
do:
|
|
339
|
+
print("runs at least once")
|
|
340
|
+
while false
|
|
237
341
|
```
|
|
238
342
|
|
|
239
|
-
###
|
|
343
|
+
### Match (Pattern Matching)
|
|
240
344
|
|
|
241
345
|
```flux
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
346
|
+
// Literal
|
|
347
|
+
match value:
|
|
348
|
+
when 0 -> print("zero")
|
|
349
|
+
when 1..9 -> print("one digit")
|
|
350
|
+
when 10..99 -> print("two digits")
|
|
351
|
+
when _ -> print("other")
|
|
352
|
+
|
|
353
|
+
// Guards
|
|
354
|
+
match score:
|
|
355
|
+
when s if s >= 90 -> print("A")
|
|
356
|
+
when s if s >= 80 -> print("B")
|
|
357
|
+
when s if s >= 70 -> print("C")
|
|
358
|
+
when _ -> print("D")
|
|
359
|
+
|
|
360
|
+
// ADT destructuring
|
|
361
|
+
match result:
|
|
362
|
+
when Ok(v) -> print("Result: {v}")
|
|
363
|
+
when Err(e) -> print("Error: {e}")
|
|
364
|
+
|
|
365
|
+
// As expression
|
|
366
|
+
val label = match status:
|
|
367
|
+
when "active" -> "Online"
|
|
368
|
+
when "inactive" -> "Offline"
|
|
369
|
+
when _ -> "Unknown"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Type System
|
|
375
|
+
|
|
376
|
+
### Union & Nullable
|
|
377
|
+
|
|
378
|
+
```flux
|
|
379
|
+
val id: Int | String = 42 // Int or String
|
|
380
|
+
val name: String? = null // String | Null shorthand
|
|
381
|
+
|
|
382
|
+
fn stringify(x: Int | String | Bool) -> String:
|
|
383
|
+
return "{x}"
|
|
245
384
|
|
|
246
385
|
fn findById(id: Int) -> String?:
|
|
247
386
|
if id > 0: return "item_{id}"
|
|
@@ -252,18 +391,115 @@ fn findById(id: Int) -> String?:
|
|
|
252
391
|
|
|
253
392
|
```flux
|
|
254
393
|
val names: Array<String> = ["Andi", "Budi", "Cici"]
|
|
255
|
-
val
|
|
394
|
+
val map: Map<String, Int> = new Map()
|
|
256
395
|
|
|
257
|
-
fn first(items: Array<
|
|
396
|
+
fn first<T>(items: Array<T>) -> T?:
|
|
258
397
|
if items.length == 0: return null
|
|
259
398
|
return items[0]
|
|
260
399
|
```
|
|
261
400
|
|
|
401
|
+
### Function Types
|
|
402
|
+
|
|
403
|
+
```flux
|
|
404
|
+
fn mapInts(arr: Array<Int>, f: fn(Int) -> Int) -> Array<Int>:
|
|
405
|
+
return arr.map(f)
|
|
406
|
+
|
|
407
|
+
// In an interface
|
|
408
|
+
interface Handler:
|
|
409
|
+
fn handle(req: String) -> String
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Inline Object Types & Tuples
|
|
413
|
+
|
|
414
|
+
```flux
|
|
415
|
+
fn makePoint(x: Float, y: Float) -> { x: Float, y: Float }:
|
|
416
|
+
return { x, y }
|
|
417
|
+
|
|
418
|
+
fn getCoord() -> [Float, Float]:
|
|
419
|
+
return [3.14, 2.71]
|
|
420
|
+
|
|
421
|
+
val [lat, lng] = getCoord()
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Intersection Types
|
|
425
|
+
|
|
426
|
+
```flux
|
|
427
|
+
interface Named: name: String
|
|
428
|
+
interface Aged: age: Int
|
|
429
|
+
|
|
430
|
+
class Person implements Named, Aged:
|
|
431
|
+
name: String
|
|
432
|
+
age: Int
|
|
433
|
+
|
|
434
|
+
// Type alias intersection
|
|
435
|
+
type Entity = Named & { id: Int }
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Conditional Types
|
|
439
|
+
|
|
440
|
+
```flux
|
|
441
|
+
// T extends U ? A : B
|
|
442
|
+
type IsString<T> = T extends String ? "yes" : "no"
|
|
443
|
+
|
|
444
|
+
// infer — extract a type from inside another
|
|
445
|
+
type Unwrap<T> = T extends Array<infer U> ? U : T
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Type Narrowing
|
|
449
|
+
|
|
450
|
+
```flux
|
|
451
|
+
val user = getUser(1) // String?
|
|
452
|
+
|
|
453
|
+
if user != null:
|
|
454
|
+
print(user.toUpperCase()) // narrowed to String here
|
|
455
|
+
|
|
456
|
+
// typeof narrowing
|
|
457
|
+
fn process(x: Int | String | Bool) -> String:
|
|
458
|
+
if typeof x == "number": return "num: {x}"
|
|
459
|
+
if typeof x == "string": return "str: {x}"
|
|
460
|
+
return "bool: {x}"
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Utility Types
|
|
464
|
+
|
|
465
|
+
```flux
|
|
466
|
+
Partial<T> // all fields optional
|
|
467
|
+
Required<T> // all fields required
|
|
468
|
+
Readonly<T> // all fields read-only
|
|
469
|
+
Record<K, V> // dictionary type
|
|
470
|
+
Pick<T, K> // keep only specified keys
|
|
471
|
+
Omit<T, K> // remove specified keys
|
|
472
|
+
NonNullable<T> // remove null from union
|
|
473
|
+
ReturnType<T> // extract return type of fn
|
|
474
|
+
Awaited<T> // unwrap Promise<T>
|
|
475
|
+
Parameters<T> // parameter types as tuple
|
|
476
|
+
Exclude<T, U> // remove U from T
|
|
477
|
+
Extract<T, U> // keep only U in T
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Index Signatures
|
|
481
|
+
|
|
482
|
+
```flux
|
|
483
|
+
fn buildRegistry() -> { [key: String]: String }:
|
|
484
|
+
return { name: "Flux", version: "3.2.0" }
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### `keyof` & `typeof`
|
|
488
|
+
|
|
489
|
+
```flux
|
|
490
|
+
fn getField(key: keyof Config) -> String:
|
|
491
|
+
return key
|
|
492
|
+
|
|
493
|
+
val config = new Config("host", "localhost")
|
|
494
|
+
fn cloneConfig(src: typeof config) -> typeof config:
|
|
495
|
+
return new Config(src.key, src.value)
|
|
496
|
+
```
|
|
497
|
+
|
|
262
498
|
---
|
|
263
499
|
|
|
264
|
-
##
|
|
500
|
+
## Interfaces
|
|
265
501
|
|
|
266
|
-
|
|
502
|
+
Interfaces define structural contracts. `flux check` reports an **error** if a required member is missing.
|
|
267
503
|
|
|
268
504
|
```flux
|
|
269
505
|
interface Printable:
|
|
@@ -271,56 +507,44 @@ interface Printable:
|
|
|
271
507
|
|
|
272
508
|
interface Shape:
|
|
273
509
|
name: String
|
|
274
|
-
fn area()
|
|
510
|
+
fn area() -> Float
|
|
275
511
|
fn perimeter() -> Float
|
|
276
512
|
|
|
277
|
-
//
|
|
513
|
+
// Inheritance
|
|
278
514
|
interface ColoredShape extends Shape:
|
|
279
515
|
color: String
|
|
280
516
|
fn setColor(c: String) -> Void
|
|
281
517
|
|
|
282
|
-
// Generic
|
|
518
|
+
// Generic
|
|
283
519
|
interface Container<T>:
|
|
284
|
-
fn get()
|
|
285
|
-
fn set(
|
|
520
|
+
fn get() -> T
|
|
521
|
+
fn set(v: T) -> Void
|
|
522
|
+
fn isEmpty() -> Bool
|
|
286
523
|
```
|
|
287
524
|
|
|
288
|
-
### Class
|
|
525
|
+
### Class Implementing Interface
|
|
289
526
|
|
|
290
527
|
```flux
|
|
291
528
|
class Rectangle implements Shape:
|
|
292
|
-
name:
|
|
293
|
-
width:
|
|
529
|
+
name: String
|
|
530
|
+
width: Float
|
|
294
531
|
height: Float
|
|
295
532
|
|
|
296
|
-
fn area() ->
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
fn perimeter() -> Float:
|
|
300
|
-
return 2.0 * (self.width + self.height)
|
|
301
|
-
|
|
302
|
-
class Circle implements Shape:
|
|
303
|
-
name: String
|
|
304
|
-
radius: Float
|
|
533
|
+
fn area() -> Float -> self.width * self.height
|
|
534
|
+
fn perimeter() -> Float -> 2.0 * (self.width + self.height)
|
|
305
535
|
|
|
306
|
-
|
|
307
|
-
return 3.14159 * self.radius * self.radius
|
|
308
|
-
|
|
309
|
-
fn perimeter() -> Float:
|
|
310
|
-
return 2.0 * 3.14159 * self.radius
|
|
311
|
-
|
|
312
|
-
val r = new Rectangle("Persegi", 5.0, 3.0)
|
|
536
|
+
val r = new Rectangle("rect", 5.0, 3.0)
|
|
313
537
|
print(r.area()) // 15
|
|
314
538
|
print(r.perimeter()) // 16
|
|
315
539
|
```
|
|
316
540
|
|
|
317
541
|
---
|
|
318
542
|
|
|
319
|
-
##
|
|
543
|
+
## Classes
|
|
320
544
|
|
|
321
545
|
```flux
|
|
322
546
|
class Animal:
|
|
323
|
-
name:
|
|
547
|
+
name: String
|
|
324
548
|
sound: String
|
|
325
549
|
|
|
326
550
|
fn speak() -> String:
|
|
@@ -342,8 +566,9 @@ print(dog.info()) // Dog: Rex (Labrador)
|
|
|
342
566
|
|
|
343
567
|
```flux
|
|
344
568
|
class BankAccount:
|
|
345
|
-
private
|
|
346
|
-
readonly
|
|
569
|
+
private balance: Float
|
|
570
|
+
readonly id: String
|
|
571
|
+
public owner: String
|
|
347
572
|
|
|
348
573
|
fn deposit(amount: Float) -> Void:
|
|
349
574
|
self.balance += amount
|
|
@@ -352,35 +577,130 @@ class BankAccount:
|
|
|
352
577
|
return self.balance
|
|
353
578
|
```
|
|
354
579
|
|
|
580
|
+
> **Private fields** compile to JavaScript `#field` syntax — enforced at the JS engine level, not just TypeScript-style erased annotations.
|
|
581
|
+
|
|
582
|
+
### Static Methods & Properties
|
|
583
|
+
|
|
584
|
+
```flux
|
|
585
|
+
class MathUtils:
|
|
586
|
+
static val PI = 3.14159
|
|
587
|
+
|
|
588
|
+
static fn circle(r: Float) -> Float:
|
|
589
|
+
return MathUtils.PI * r * r
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Getter / Setter
|
|
593
|
+
|
|
594
|
+
```flux
|
|
595
|
+
class Temperature:
|
|
596
|
+
private celsius: Float
|
|
597
|
+
|
|
598
|
+
get fahrenheit() -> Float:
|
|
599
|
+
return self.celsius * 9.0 / 5.0 + 32.0
|
|
600
|
+
|
|
601
|
+
set fahrenheit(f: Float):
|
|
602
|
+
self.celsius = (f - 32.0) * 5.0 / 9.0
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Generic Classes
|
|
606
|
+
|
|
607
|
+
```flux
|
|
608
|
+
class Stack<T>:
|
|
609
|
+
private items: Array<T> = []
|
|
610
|
+
|
|
611
|
+
fn push(item: T) -> Void:
|
|
612
|
+
self.items.push(item)
|
|
613
|
+
|
|
614
|
+
fn pop() -> T?:
|
|
615
|
+
return self.items.pop()
|
|
616
|
+
|
|
617
|
+
get size() -> Int:
|
|
618
|
+
return self.items.length
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Decorators
|
|
624
|
+
|
|
625
|
+
Decorators wrap functions and classes to add behaviour without modifying their source.
|
|
626
|
+
|
|
627
|
+
```flux
|
|
628
|
+
// @name — apply decorator after definition
|
|
629
|
+
@log
|
|
630
|
+
fn fetchUser(id: Int):
|
|
631
|
+
return fetch("/api/users/{id}")
|
|
632
|
+
|
|
633
|
+
// @name(args) — decorator factory (called with args, returns decorator)
|
|
634
|
+
@deprecated("use fetchUserV2 instead")
|
|
635
|
+
fn fetchUser(id):
|
|
636
|
+
return fetch("/api/users/{id}")
|
|
637
|
+
|
|
638
|
+
// Class decorator
|
|
639
|
+
@injectable
|
|
640
|
+
class UserService:
|
|
641
|
+
var db: Database
|
|
642
|
+
|
|
643
|
+
// Multiple decorators (applied bottom-up)
|
|
644
|
+
@singleton
|
|
645
|
+
@injectable
|
|
646
|
+
class AppService:
|
|
647
|
+
var repo: Repository
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**How it compiles:**
|
|
651
|
+
|
|
652
|
+
```javascript
|
|
653
|
+
// @log fn greet() →
|
|
654
|
+
function greet(name) { ... }
|
|
655
|
+
greet = log(greet);
|
|
656
|
+
|
|
657
|
+
// @deprecated("...") fn foo() →
|
|
658
|
+
function foo() { ... }
|
|
659
|
+
foo = deprecated("use fetchUserV2 instead")(foo);
|
|
660
|
+
|
|
661
|
+
// @injectable class Service →
|
|
662
|
+
class Service { ... }
|
|
663
|
+
Service = injectable(Service);
|
|
664
|
+
```
|
|
665
|
+
|
|
355
666
|
---
|
|
356
667
|
|
|
357
668
|
## Algebraic Data Types (ADT)
|
|
358
669
|
|
|
359
670
|
```flux
|
|
360
|
-
//
|
|
671
|
+
// Definition
|
|
361
672
|
type Option = Some(value) | None
|
|
362
|
-
type Result = Ok(value)
|
|
673
|
+
type Result = Ok(value) | Err(message)
|
|
363
674
|
type Shape = Circle(radius) | Rectangle(w, h) | Triangle(base, height)
|
|
675
|
+
|
|
676
|
+
// Generic ADTs
|
|
677
|
+
type Result<T> = Ok(value) | Err(message)
|
|
678
|
+
type Either<L, R> = Left(value) | Right(value)
|
|
364
679
|
```
|
|
365
680
|
|
|
366
|
-
### Pattern Matching
|
|
681
|
+
### Pattern Matching with ADT
|
|
367
682
|
|
|
368
683
|
```flux
|
|
369
684
|
fn safeDivide(a: Float, b: Float) -> Result:
|
|
370
685
|
if b == 0.0: return Err("Division by zero")
|
|
371
686
|
return Ok(a / b)
|
|
372
687
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
when Ok(v) -> print("Result: {v}")
|
|
377
|
-
when Err(e) -> print("Error: {e}")
|
|
688
|
+
match safeDivide(10.0, 2.0):
|
|
689
|
+
when Ok(v) -> print("Result: {v}")
|
|
690
|
+
when Err(e) -> print("Error: {e}")
|
|
378
691
|
|
|
379
|
-
//
|
|
380
|
-
match
|
|
381
|
-
when Ok(v) if v > 0
|
|
692
|
+
// Guards
|
|
693
|
+
match safeDivide(10.0, 2.0):
|
|
694
|
+
when Ok(v) if v > 0 -> print("Positive: {v}")
|
|
382
695
|
when Ok(v) -> print("Non-positive: {v}")
|
|
383
696
|
when Err(e) -> print("Error: {e}")
|
|
697
|
+
|
|
698
|
+
// Shape area using ADT
|
|
699
|
+
fn area(shape: Shape) -> Float:
|
|
700
|
+
match shape:
|
|
701
|
+
when Circle(r) -> 3.14159 * r * r
|
|
702
|
+
when Rectangle(w,h) -> w * h
|
|
703
|
+
when Triangle(b,h) -> 0.5 * b * h
|
|
384
704
|
```
|
|
385
705
|
|
|
386
706
|
---
|
|
@@ -399,63 +719,46 @@ enum Status:
|
|
|
399
719
|
Inactive
|
|
400
720
|
Pending
|
|
401
721
|
|
|
402
|
-
|
|
403
|
-
|
|
722
|
+
enum Color:
|
|
723
|
+
Red // 0
|
|
724
|
+
Green = 10 // 10
|
|
725
|
+
Blue // 11 (auto-increments from explicit value)
|
|
726
|
+
|
|
727
|
+
print(Direction.North) // 0
|
|
728
|
+
print(Status.Active) // 0
|
|
729
|
+
|
|
730
|
+
// Enums in match
|
|
731
|
+
match dir:
|
|
732
|
+
when Direction.North -> print("Going north")
|
|
733
|
+
when Direction.South -> print("Going south")
|
|
734
|
+
when _ -> print("Other direction")
|
|
404
735
|
```
|
|
405
736
|
|
|
406
737
|
---
|
|
407
738
|
|
|
408
|
-
##
|
|
409
|
-
|
|
410
|
-
### If / Else
|
|
411
|
-
|
|
412
|
-
```flux
|
|
413
|
-
if x > 0:
|
|
414
|
-
print("positif")
|
|
415
|
-
else if x < 0:
|
|
416
|
-
print("negatif")
|
|
417
|
-
else:
|
|
418
|
-
print("nol")
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### For / While / Do-While
|
|
739
|
+
## Destructuring & Spread
|
|
422
740
|
|
|
423
741
|
```flux
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// Range
|
|
429
|
-
for i in 0..10:
|
|
430
|
-
print(i)
|
|
742
|
+
// Object destructuring
|
|
743
|
+
val person = { name: "Budi", age: 25, city: "Jakarta" }
|
|
744
|
+
val { name, age } = person
|
|
431
745
|
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
while n > 0:
|
|
435
|
-
print(n)
|
|
436
|
-
n -= 1
|
|
746
|
+
// With alias
|
|
747
|
+
val { name: personName, age: personAge } = person
|
|
437
748
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
print("minimal sekali")
|
|
441
|
-
while false
|
|
442
|
-
```
|
|
749
|
+
// Array destructuring
|
|
750
|
+
val [first, second, ...rest] = [1, 2, 3, 4, 5]
|
|
443
751
|
|
|
444
|
-
|
|
752
|
+
// With default value
|
|
753
|
+
val { title = "Untitled", content } = post
|
|
445
754
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
when 1..9 -> print("satu digit")
|
|
450
|
-
when 10..99 -> print("dua digit")
|
|
451
|
-
when _ -> print("lainnya")
|
|
755
|
+
// In function params
|
|
756
|
+
fn greet({ name, title = "Mr" }) -> String:
|
|
757
|
+
return "Hello, {title} {name}!"
|
|
452
758
|
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
when s if s >= 80 -> print("B")
|
|
457
|
-
when s if s >= 70 -> print("C")
|
|
458
|
-
when _ -> print("D")
|
|
759
|
+
// Spread — shallow merge
|
|
760
|
+
val defaults_ = { role: "viewer", active: true }
|
|
761
|
+
val merged = { ...defaults_, ...overrides }
|
|
459
762
|
```
|
|
460
763
|
|
|
461
764
|
---
|
|
@@ -468,317 +771,419 @@ try:
|
|
|
468
771
|
val data = JSON.parse(input)
|
|
469
772
|
print(data)
|
|
470
773
|
catch(e):
|
|
471
|
-
print("
|
|
774
|
+
print("Parse error: {e.message}")
|
|
472
775
|
finally:
|
|
473
|
-
print("
|
|
776
|
+
print("Done")
|
|
474
777
|
|
|
475
778
|
// Throw
|
|
476
779
|
fn validateAge(age: Int) -> Void:
|
|
477
780
|
if age < 0:
|
|
478
|
-
throw new Error("
|
|
781
|
+
throw new Error("Age cannot be negative: {age}")
|
|
479
782
|
|
|
480
|
-
//
|
|
783
|
+
// ADT Result pattern (no exceptions)
|
|
481
784
|
type Result = Ok(value) | Err(message)
|
|
482
785
|
|
|
483
786
|
fn parseNumber(s: String) -> Result:
|
|
484
787
|
val n = parseInt(s, 10)
|
|
485
|
-
if isNaN(n): return Err("
|
|
788
|
+
if isNaN(n): return Err("Not a number: {s}")
|
|
486
789
|
return Ok(n)
|
|
790
|
+
|
|
791
|
+
match parseNumber("42"):
|
|
792
|
+
when Ok(n) -> print("Got: {n}")
|
|
793
|
+
when Err(e) -> print("Failed: {e}")
|
|
487
794
|
```
|
|
488
795
|
|
|
489
796
|
---
|
|
490
797
|
|
|
491
|
-
##
|
|
798
|
+
## Async / Await
|
|
492
799
|
|
|
493
800
|
```flux
|
|
494
|
-
|
|
495
|
-
val
|
|
496
|
-
val
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
801
|
+
async fn fetchUser(id: Int):
|
|
802
|
+
val res = await fetch("/api/users/{id}")
|
|
803
|
+
val user = await res.json()
|
|
804
|
+
return user
|
|
805
|
+
|
|
806
|
+
// Parallel requests
|
|
807
|
+
async fn loadDashboard():
|
|
808
|
+
val [user, posts, config] = await Promise.all([
|
|
809
|
+
fetchUser(1),
|
|
810
|
+
fetchPosts(),
|
|
811
|
+
fetchConfig()
|
|
812
|
+
])
|
|
813
|
+
print("Loaded: {user.name}, {posts.length} posts")
|
|
814
|
+
|
|
815
|
+
// Error handling in async
|
|
816
|
+
async fn safeLoad(url: String):
|
|
817
|
+
try:
|
|
818
|
+
val res = await fetch(url)
|
|
819
|
+
return await res.json()
|
|
820
|
+
catch(e):
|
|
821
|
+
print("Network error: {e.message}")
|
|
822
|
+
return null
|
|
506
823
|
```
|
|
507
824
|
|
|
508
825
|
---
|
|
509
826
|
|
|
510
|
-
##
|
|
827
|
+
## Modules
|
|
511
828
|
|
|
512
829
|
```flux
|
|
513
830
|
// Import default
|
|
514
831
|
import express from 'express'
|
|
515
832
|
|
|
516
|
-
//
|
|
833
|
+
// Named imports
|
|
517
834
|
import { readFileSync, writeFileSync } from 'fs'
|
|
518
835
|
|
|
519
|
-
//
|
|
836
|
+
// Namespace import
|
|
520
837
|
import * as path from 'path'
|
|
521
838
|
|
|
522
|
-
//
|
|
839
|
+
// Aliased import
|
|
840
|
+
import { useState as useLocalState } from 'react'
|
|
841
|
+
|
|
842
|
+
// Export named
|
|
523
843
|
export fn calculateTax(amount: Float, rate: Float) -> Float:
|
|
524
844
|
return amount * rate
|
|
525
845
|
|
|
846
|
+
export val TAX_RATE = 0.1
|
|
847
|
+
|
|
848
|
+
// Export default
|
|
526
849
|
export default fn main():
|
|
527
850
|
print("Hello from main!")
|
|
528
851
|
```
|
|
529
852
|
|
|
530
853
|
---
|
|
531
854
|
|
|
532
|
-
##
|
|
855
|
+
## Pipe Operator
|
|
533
856
|
|
|
534
|
-
|
|
857
|
+
The `|>` operator passes the left value as the first argument to the right function. Combine with stdlib for expressive data pipelines.
|
|
535
858
|
|
|
536
859
|
```flux
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
860
|
+
// Chained transformations
|
|
861
|
+
val total = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
862
|
+
|> filter(n -> n % 2 == 0)
|
|
863
|
+
|> map(n -> n * n)
|
|
864
|
+
|> reduce((acc, n) -> acc + n, 0) // 220
|
|
865
|
+
|
|
866
|
+
// String pipeline
|
|
867
|
+
val slug = " Hello World "
|
|
868
|
+
|> trim
|
|
869
|
+
|> s -> s.toLowerCase()
|
|
870
|
+
|> s -> s.replace(/\s+/g, "-") // "hello-world"
|
|
871
|
+
|
|
872
|
+
// With stdlib functions
|
|
873
|
+
val topScorers = students
|
|
874
|
+
|> sortBy(s -> s.score)
|
|
875
|
+
|> s -> s.reverse()
|
|
876
|
+
|> take(3)
|
|
877
|
+
|> map(s -> s.name)
|
|
549
878
|
```
|
|
550
879
|
|
|
551
880
|
---
|
|
552
881
|
|
|
553
|
-
## JSX
|
|
882
|
+
## JSX
|
|
883
|
+
|
|
884
|
+
Configure the target in `flux.config.js`: `jsxTarget: "react"` for React, `jsxTarget: "server"` for server-side string rendering.
|
|
554
885
|
|
|
555
886
|
```flux
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
887
|
+
// React component
|
|
888
|
+
fn Button({ label, onClick, disabled = false }):
|
|
889
|
+
return (
|
|
890
|
+
<button
|
|
891
|
+
onClick={onClick}
|
|
892
|
+
disabled={disabled}
|
|
893
|
+
className={disabled ? 'btn disabled' : 'btn'}
|
|
894
|
+
>
|
|
895
|
+
{label}
|
|
896
|
+
</button>
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
// Conditional rendering
|
|
900
|
+
fn UserCard({ user }):
|
|
901
|
+
return (
|
|
902
|
+
<div className="card">
|
|
903
|
+
<h2>{user.name}</h2>
|
|
904
|
+
{user.email && <p>{user.email}</p>}
|
|
905
|
+
</div>
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
// List rendering
|
|
909
|
+
fn ItemList({ items }):
|
|
910
|
+
return (
|
|
911
|
+
<ul>
|
|
912
|
+
{items.map(item -> <li key={item.id}>{item.name}</li>)}
|
|
913
|
+
</ul>
|
|
914
|
+
)
|
|
562
915
|
```
|
|
563
916
|
|
|
564
917
|
---
|
|
565
918
|
|
|
566
|
-
## CSS
|
|
919
|
+
## CSS-in-Flux
|
|
920
|
+
|
|
921
|
+
The `css {}` block is a Flux-native CSS preprocessor. Supports shorthand properties and standard CSS with Flux sugar.
|
|
567
922
|
|
|
568
923
|
```flux
|
|
569
|
-
val styles = css
|
|
570
|
-
.
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
924
|
+
val styles = css {
|
|
925
|
+
.card {
|
|
926
|
+
flex; center; bold
|
|
927
|
+
bg: #0f172a
|
|
928
|
+
radius: 12px
|
|
929
|
+
p: 20px
|
|
930
|
+
border: 1px solid #1e3a5f
|
|
574
931
|
|
|
575
932
|
&:hover {
|
|
576
|
-
|
|
933
|
+
border-color: #38bdf8
|
|
577
934
|
}
|
|
578
935
|
}
|
|
579
|
-
`
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
---
|
|
583
936
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
| `flux compile file.flux -o out` | Compile ke file output tertentu |
|
|
591
|
-
| `flux bundle main.flux` | Bundle semua import menjadi satu file |
|
|
592
|
-
| `flux run file.flux` | Compile dan langsung jalankan |
|
|
593
|
-
| `flux watch file.flux` | Pantau perubahan dan compile otomatis |
|
|
594
|
-
| `flux check file.flux` | **Type check** + analisis statis |
|
|
595
|
-
| `flux lint file.flux` | **Lint** — unused vars, unreachable code, shadowing |
|
|
596
|
-
| `flux fmt file.flux` | Format kode sumber |
|
|
597
|
-
| `flux test [dir]` | Temukan dan jalankan file `*.test.flux` |
|
|
598
|
-
| `flux tokens file.flux` | Tampilkan daftar token dari lexer |
|
|
599
|
-
| `flux ast file.flux` | Tampilkan AST dalam format JSON |
|
|
600
|
-
| `flux repl` | Mode REPL interaktif |
|
|
601
|
-
| `flux version` | Tampilkan versi |
|
|
602
|
-
|
|
603
|
-
### Flag
|
|
937
|
+
.button {
|
|
938
|
+
bg: linear-gradient(135deg, #38bdf8, #818cf8)
|
|
939
|
+
radius: 8px
|
|
940
|
+
p: 10px 20px
|
|
941
|
+
color: white
|
|
942
|
+
bold
|
|
604
943
|
|
|
944
|
+
&:hover { opacity: 0.85 }
|
|
945
|
+
}
|
|
946
|
+
}
|
|
605
947
|
```
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
--stdout Cetak output ke terminal
|
|
609
|
-
--no-color Nonaktifkan warna
|
|
610
|
-
```
|
|
948
|
+
|
|
949
|
+
**Shorthand properties:** `flex` → `display:flex`, `bold` → `font-weight:700`, `center` → `text-align:center`, `bg` → `background`, `radius` → `border-radius`, `p` → `padding`, `m` → `margin`.
|
|
611
950
|
|
|
612
951
|
---
|
|
613
952
|
|
|
614
|
-
##
|
|
953
|
+
## Standard Library
|
|
615
954
|
|
|
616
|
-
|
|
955
|
+
All stdlib functions are **tree-shaken** — only symbols actually used in compiled output are injected.
|
|
617
956
|
|
|
618
|
-
###
|
|
957
|
+
### Sequences
|
|
619
958
|
|
|
620
959
|
```flux
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
960
|
+
range(end) // range(5) → [0,1,2,3,4]
|
|
961
|
+
range(start, end) // range(1, 4) → [1,2,3]
|
|
962
|
+
range(start, end, step) // range(0, 10, 2) → [0,2,4,6,8]
|
|
963
|
+
zip(...arrays) // zip([1,2],[3,4]) → [[1,3],[2,4]]
|
|
964
|
+
unzip(pairs) // unzip([[1,"a"]]) → [[1],["a"]]
|
|
965
|
+
enumerate(arr, start?) // enumerate(["a","b"]) → [[0,"a"],[1,"b"]]
|
|
966
|
+
flatten(arr, depth?) // flatten([[1,[2]],3]) → [1,2,3]
|
|
967
|
+
chunk(arr, size) // chunk([1..5], 2) → [[1,2],[3,4],[5]]
|
|
968
|
+
unique(arr) // unique([1,2,2,3]) → [1,2,3]
|
|
969
|
+
groupBy(arr, fn|key) // groupBy(items, "type") → { fruit:[...], veg:[...] }
|
|
970
|
+
sortBy(arr, fn|key) // sortBy(users, "age")
|
|
971
|
+
countBy(arr, fn|key) // countBy(words, w -> w[0]) → { a:3, b:2 }
|
|
972
|
+
partition(arr, fn) // partition(nums, n -> n>0) → [[pos],[neg]]
|
|
973
|
+
toPairs(obj) // toPairs({a:1}) → [["a",1]]
|
|
631
974
|
```
|
|
632
975
|
|
|
633
|
-
###
|
|
976
|
+
### Arrays (pipe-compatible)
|
|
634
977
|
|
|
635
978
|
```flux
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
fn
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
979
|
+
// Pipe helpers — designed for |>
|
|
980
|
+
map(arr, fn) // [1,2,3] |> map(x -> x*2)
|
|
981
|
+
filter(arr, fn) // nums |> filter(x -> x > 0)
|
|
982
|
+
reduce(arr, fn, init?) // nums |> reduce((a,n) -> a+n, 0)
|
|
983
|
+
forEach(arr, fn) // items |> forEach(print)
|
|
984
|
+
find(arr, fn) // users |> find(u -> u.id == id)
|
|
985
|
+
findIndex(arr, fn) // arr |> findIndex(x -> x == 5)
|
|
986
|
+
some(arr, fn) // arr |> some(x -> x > 10)
|
|
987
|
+
every(arr, fn) // arr |> every(x -> x > 0)
|
|
988
|
+
join(arr, sep?) // words |> join(", ")
|
|
989
|
+
sort(arr, fn?) // nums |> sort()
|
|
990
|
+
flat(arr, depth?) // nested |> flat()
|
|
991
|
+
flatMap(arr, fn) // arrs |> flatMap(x -> x)
|
|
992
|
+
includes(arr, val) // arr |> includes(5)
|
|
993
|
+
|
|
994
|
+
// Array extras
|
|
995
|
+
first(arr) / head(arr) // first([1,2,3]) → 1
|
|
996
|
+
last(arr) // last([1,2,3]) → 3
|
|
997
|
+
tail(arr) // tail([1,2,3]) → [2,3]
|
|
998
|
+
nth(arr, n) // nth([1,2,3], -1) → 3
|
|
999
|
+
take(arr, n) // take([1,2,3,4], 2) → [1,2]
|
|
1000
|
+
drop(arr, n) // drop([1,2,3,4], 2) → [3,4]
|
|
1001
|
+
takeWhile(arr, fn) // takeWhile([1,2,5,3], n -> n<4) → [1,2]
|
|
1002
|
+
dropWhile(arr, fn) // dropWhile([1,2,5,3], n -> n<4) → [5,3]
|
|
1003
|
+
compact(arr) // compact([0,1,null,2,""]) → [1,2]
|
|
1004
|
+
intersection(a, b) // intersection([1,2,3],[2,3,4]) → [2,3]
|
|
1005
|
+
difference(a, b) // difference([1,2,3],[2,3]) → [1]
|
|
1006
|
+
arrayUnion(a, b) // arrayUnion([1,2],[2,3]) → [1,2,3]
|
|
1007
|
+
count(arr, fn?) // count(arr, x -> x > 0)
|
|
1008
|
+
minBy(arr, fn|key) // minBy(users, u -> u.age)
|
|
1009
|
+
maxBy(arr, fn|key) // maxBy(products, p -> p.price)
|
|
1010
|
+
rotate(arr, n) // rotate([1,2,3,4], 1) → [2,3,4,1]
|
|
1011
|
+
sliding(arr, size, step?) // sliding([1,2,3,4], 2) → [[1,2],[2,3],[3,4]]
|
|
645
1012
|
```
|
|
646
1013
|
|
|
647
|
-
###
|
|
1014
|
+
### Math & Random
|
|
648
1015
|
|
|
649
1016
|
```flux
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1017
|
+
clamp(val, min, max) // clamp(150, 0, 100) → 100
|
|
1018
|
+
sum(arr) // sum([1,2,3,4]) → 10
|
|
1019
|
+
product(arr) // product([1,2,3,4]) → 24
|
|
1020
|
+
min(arr) / min(a, b, ...) // min([3,1,4]) → 1
|
|
1021
|
+
max(arr) / max(a, b, ...) // max([3,1,4]) → 4
|
|
1022
|
+
abs(n) // abs(-5) → 5
|
|
1023
|
+
floor(n) // floor(3.7) → 3
|
|
1024
|
+
ceil(n) // ceil(3.2) → 4
|
|
1025
|
+
round(n, decimals?) // round(3.14159, 2) → 3.14
|
|
1026
|
+
mean(arr) // mean([1,2,3,4,5]) → 3
|
|
1027
|
+
median(arr) // median([1,3,5]) → 3
|
|
1028
|
+
stdDev(arr) // stdDev([2,4,4,4,5]) → ~0.97
|
|
1029
|
+
lerp(a, b, t) // lerp(0, 100, 0.5) → 50
|
|
1030
|
+
randInt(min, max) // randInt(1, 6) → dice roll
|
|
1031
|
+
sample(arr) // sample(["a","b","c"]) → random element
|
|
1032
|
+
shuffle(arr) // shuffle(range(1, 53)) → shuffled deck
|
|
657
1033
|
```
|
|
658
1034
|
|
|
659
|
-
|
|
1035
|
+
> **`Math` object** — all JS `Math` functions are also directly available: `Math.sqrt`, `Math.pow`, `Math.log`, `Math.sin`, `Math.cos`, `Math.PI`, etc.
|
|
660
1036
|
|
|
661
|
-
|
|
662
|
-
interface Printable:
|
|
663
|
-
fn toString() -> String
|
|
1037
|
+
### Objects
|
|
664
1038
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1039
|
+
```flux
|
|
1040
|
+
keys(obj) // keys({a:1,b:2}) → ["a","b"]
|
|
1041
|
+
values(obj) // values({a:1,b:2}) → [1,2]
|
|
1042
|
+
entries(obj) // entries({a:1}) → [["a",1]]
|
|
1043
|
+
pick(obj, keys) // pick(user, ["name","role"])
|
|
1044
|
+
omit(obj, keys) // omit(user, ["password"])
|
|
1045
|
+
merge(obj, ...sources) // merge(defaults, overrides)
|
|
1046
|
+
defaults(obj, ...sources) // fill null/undefined only
|
|
1047
|
+
invert(obj) // invert({ok:200}) → {"200":"ok"}
|
|
1048
|
+
mapValues(obj, fn) // mapValues(scores, v -> v*2)
|
|
1049
|
+
filterValues(obj, fn) // filterValues(obj, v -> v > 0)
|
|
1050
|
+
fromEntries(entries) // fromEntries([["x",1]]) → {x:1}
|
|
1051
|
+
deepEqual(a, b) // deepEqual({a:1}, {a:1}) → true
|
|
1052
|
+
deepClone(v) // deep copy via JSON round-trip
|
|
669
1053
|
```
|
|
670
1054
|
|
|
671
|
-
###
|
|
1055
|
+
### Strings
|
|
672
1056
|
|
|
673
1057
|
```flux
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1058
|
+
capitalize(s) // capitalize("hello") → "Hello"
|
|
1059
|
+
camelCase(s) // camelCase("hello world") → "helloWorld"
|
|
1060
|
+
snakeCase(s) // snakeCase("helloWorld") → "hello_world"
|
|
1061
|
+
kebabCase(s) // kebabCase("helloWorld") → "hello-world"
|
|
1062
|
+
truncate(s, len, suffix?) // truncate("long text", 8) → "long ..."
|
|
1063
|
+
pad(s, len, char?) // pad("hi", 6) → " hi "
|
|
1064
|
+
padStart(s, len, char?) // padStart("42", 5, "0") → "00042"
|
|
1065
|
+
padEnd(s, len, char?) // padEnd("hi", 5) → "hi "
|
|
1066
|
+
trim(s) // trim(" hi ") → "hi"
|
|
1067
|
+
trimStart(s) // trimStart(" hi") → "hi"
|
|
1068
|
+
trimEnd(s) // trimEnd("hi ") → "hi"
|
|
1069
|
+
words(s) // words("hello world") → ["hello","world"]
|
|
1070
|
+
lines(s) // lines("a\nb\nc") → ["a","b","c"]
|
|
1071
|
+
startsWith(s, prefix) // startsWith("flux", "fl") → true
|
|
1072
|
+
endsWith(s, suffix) // endsWith("flux", "ux") → true
|
|
1073
|
+
repeat(s, n) // repeat("ab", 3) → "ababab"
|
|
1074
|
+
replaceAll(s, find, rep) // replaceAll("aabba", "a", "x") → "xxbbx"
|
|
1075
|
+
reverseStr(s) // reverseStr("flux") → "xulf"
|
|
1076
|
+
```
|
|
677
1077
|
|
|
678
|
-
|
|
1078
|
+
> **Native string methods** also available: `.toUpperCase()`, `.toLowerCase()`, `.split()`, `.replace()`, `.includes()`, `.slice()`, `.indexOf()`, `.match()`.
|
|
679
1079
|
|
|
680
|
-
|
|
681
|
-
if user != null:
|
|
682
|
-
print(user.toUpperCase()) // OK — user adalah String di sini, bukan String?
|
|
1080
|
+
### Functional
|
|
683
1081
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1082
|
+
```flux
|
|
1083
|
+
pipe(f1, f2, ...) // compose left-to-right → new function
|
|
1084
|
+
compose(f1, f2, ...) // compose right-to-left → new function
|
|
1085
|
+
partial(fn, ...args) // pre-fill first N arguments
|
|
1086
|
+
curry(fn) // auto-curry — one arg at a time
|
|
1087
|
+
memoize(fn) // cache results by JSON-serialized args
|
|
1088
|
+
once(fn) // execute only on first call; cache result
|
|
1089
|
+
flip(fn) // swap first two arguments
|
|
1090
|
+
complement(fn) // negate fn's boolean result
|
|
1091
|
+
identity(v) // return v unchanged
|
|
1092
|
+
noop() // do nothing
|
|
689
1093
|
```
|
|
690
1094
|
|
|
691
|
-
### Function Types
|
|
692
|
-
|
|
693
1095
|
```flux
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1096
|
+
// pipe example
|
|
1097
|
+
val process = pipe(
|
|
1098
|
+
s -> s.trim(),
|
|
1099
|
+
s -> s.toLowerCase(),
|
|
1100
|
+
capitalize
|
|
1101
|
+
)
|
|
1102
|
+
process(" hello WORLD ") // "Hello world"
|
|
1103
|
+
|
|
1104
|
+
// once — run initialization only once
|
|
1105
|
+
val init = once(() -> {
|
|
1106
|
+
print("connecting...")
|
|
1107
|
+
return connectDB()
|
|
1108
|
+
})
|
|
1109
|
+
init() // "connecting..." → connection
|
|
1110
|
+
init() // silent → same connection
|
|
1111
|
+
|
|
1112
|
+
// complement — negate a predicate
|
|
1113
|
+
val isEven = n -> n % 2 == 0
|
|
1114
|
+
val isOdd = complement(isEven)
|
|
1115
|
+
[1,2,3,4] |> filter(isOdd) // [1,3]
|
|
1116
|
+
|
|
1117
|
+
// curry
|
|
1118
|
+
val add = curry((a, b) -> a + b)
|
|
1119
|
+
val addFive = add(5)
|
|
1120
|
+
addFive(3) // 8
|
|
706
1121
|
```
|
|
707
1122
|
|
|
708
|
-
###
|
|
1123
|
+
### Type Checks
|
|
709
1124
|
|
|
710
1125
|
```flux
|
|
711
|
-
//
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
//
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
return new Config(src.key, src.value)
|
|
1126
|
+
isNil(v) // true if null or undefined
|
|
1127
|
+
isString(v) // true if typeof v === "string"
|
|
1128
|
+
isNumber(v) // true if finite number (excludes NaN)
|
|
1129
|
+
isArray(v) // true if Array.isArray(v)
|
|
1130
|
+
isObject(v) // true if plain object (not array/null)
|
|
1131
|
+
isFunction(v) // true if typeof v === "function"
|
|
1132
|
+
isBool(v) // true if typeof v === "boolean"
|
|
719
1133
|
```
|
|
720
1134
|
|
|
721
|
-
### Utility Types
|
|
722
|
-
|
|
723
1135
|
```flux
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
fn setRequired(opts: Required<Config>) -> Void:
|
|
730
|
-
// semua field wajib
|
|
731
|
-
pass
|
|
732
|
-
|
|
733
|
-
fn makeReadonly(opts: Readonly<Config>) -> Void:
|
|
734
|
-
// tidak bisa diubah
|
|
735
|
-
pass
|
|
736
|
-
|
|
737
|
-
// Record<K, V> — dictionary type
|
|
738
|
-
fn buildMap(keys: Array<String>) -> Record<String, Int>:
|
|
739
|
-
return {}
|
|
740
|
-
|
|
741
|
-
// NonNullable<T> — hapus null dari union
|
|
742
|
-
fn safeGet(x: NonNullable<String?>) -> String:
|
|
743
|
-
return x
|
|
744
|
-
|
|
745
|
-
// ReturnType<T> — extract return type
|
|
746
|
-
// Pick<T, K>, Omit<T, K>, Exclude<T, U>, Extract<T, U>
|
|
747
|
-
// Awaited<T>, Parameters<T>, InstanceType<T>
|
|
1136
|
+
fn safeLength(v) -> Int:
|
|
1137
|
+
if isString(v): return v.length
|
|
1138
|
+
if isArray(v): return v.length
|
|
1139
|
+
return 0
|
|
748
1140
|
```
|
|
749
1141
|
|
|
750
|
-
###
|
|
1142
|
+
### Async Utils
|
|
751
1143
|
|
|
752
1144
|
```flux
|
|
753
|
-
//
|
|
754
|
-
fn
|
|
755
|
-
|
|
1145
|
+
sleep(ms) // Promise that resolves after ms
|
|
1146
|
+
retry(fn, attempts?, delay?) // retry up to N times with exponential back-off
|
|
1147
|
+
timeout(fn|promise, ms) // reject with Error if exceeds ms
|
|
1148
|
+
allSettled(promises) // like Promise.allSettled — never rejects
|
|
1149
|
+
debounce(fn, ms) // delay until ms of inactivity (trailing edge)
|
|
1150
|
+
throttle(fn, ms) // fire at most once per ms
|
|
1151
|
+
memoize(fn) // cache async results too
|
|
756
1152
|
```
|
|
757
1153
|
|
|
758
|
-
### Generic Type Parameters di ADT
|
|
759
|
-
|
|
760
1154
|
```flux
|
|
761
|
-
//
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1155
|
+
// sleep — non-blocking delay
|
|
1156
|
+
await sleep(2000) // wait 2 seconds
|
|
1157
|
+
|
|
1158
|
+
// retry — defaults: 3 attempts, 300ms base delay, exponential back-off
|
|
1159
|
+
val data = await retry(
|
|
1160
|
+
() -> fetchDataFromAPI(),
|
|
1161
|
+
5, // max attempts
|
|
1162
|
+
200 // base delay ms (doubles each retry)
|
|
1163
|
+
)
|
|
765
1164
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1165
|
+
// timeout — fail if too slow
|
|
1166
|
+
val res = await timeout(fetchFromSlowAPI(), 5000)
|
|
1167
|
+
|
|
1168
|
+
// debounce — fire after user stops typing
|
|
1169
|
+
val onSearch = debounce(q -> search(q), 300)
|
|
1170
|
+
|
|
1171
|
+
// allSettled — collect every outcome without throwing
|
|
1172
|
+
val results = await allSettled([fetchUser(), fetchPosts()])
|
|
1173
|
+
for r in results:
|
|
1174
|
+
if r.status == "fulfilled": print("ok: {r.value}")
|
|
1175
|
+
else: print("err: {r.reason}")
|
|
769
1176
|
```
|
|
770
1177
|
|
|
771
1178
|
---
|
|
772
1179
|
|
|
773
1180
|
## `flux check` — Type Checker
|
|
774
1181
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
1. **Syntax check** — pastikan kode bisa diparse
|
|
778
|
-
2. **Val immutability** — cegah reassignment `val`
|
|
779
|
-
3. **Type checker** — periksa tipe annotations, return types, interface implementations, dan type narrowing
|
|
1182
|
+
`flux check` runs three layers:
|
|
780
1183
|
|
|
781
|
-
|
|
1184
|
+
1. **Syntax check** — ensure code is parseable
|
|
1185
|
+
2. **Val immutability** — prevent reassignment of `val`
|
|
1186
|
+
3. **Type checker** — validate type annotations, return types, interface implementations, and type narrowing
|
|
782
1187
|
|
|
783
1188
|
```
|
|
784
1189
|
$ flux check app.flux
|
|
@@ -792,251 +1197,138 @@ $ flux check app.flux
|
|
|
792
1197
|
|
|
793
1198
|
[TypeError]:28:1 Class 'Cat' does not implement method 'area()' required by interface 'Shape'
|
|
794
1199
|
hint: Add 'fn area()' to the class
|
|
795
|
-
|
|
796
|
-
✗ app.flux — 2 type errors
|
|
797
|
-
Functions: 5 | Classes: 2 | JS output: 47 lines
|
|
798
1200
|
```
|
|
799
1201
|
|
|
800
|
-
###
|
|
1202
|
+
### What is Checked
|
|
801
1203
|
|
|
802
|
-
|
|
|
803
|
-
|
|
804
|
-
|
|
|
805
|
-
| Return type mismatch
|
|
806
|
-
| Interface
|
|
807
|
-
| Val reassignment
|
|
808
|
-
| Nullable
|
|
1204
|
+
| Check | Example caught |
|
|
1205
|
+
|---|---|
|
|
1206
|
+
| Variable type mismatch | `val name: String = 42` → error |
|
|
1207
|
+
| Return type mismatch | `fn f() -> Int: return "hello"` → error |
|
|
1208
|
+
| Interface not implemented | class missing a required method |
|
|
1209
|
+
| Val reassignment | `val x = 1; x = 2` → error |
|
|
1210
|
+
| Nullable assignability | `val x: Int = null` → error |
|
|
809
1211
|
|
|
810
1212
|
---
|
|
811
1213
|
|
|
812
1214
|
## `flux lint` — Linter
|
|
813
1215
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
| Lapisan | Aturan | Keparahan |
|
|
817
|
-
|---------|--------|-----------|
|
|
818
|
-
| Syntax | Parse errors | Error (fatal) |
|
|
819
|
-
| Immutability | Reassignment `val` | Error |
|
|
820
|
-
| Type checker | Mismatch tipe, interface | Error |
|
|
821
|
-
| **unused-var** | `val`/`var` dideklarasi tapi tidak pernah dibaca | Warning |
|
|
822
|
-
| **unreachable** | Statement setelah `return`/`throw`/`break` | Error |
|
|
823
|
-
| **shadow-val** | `val x` di scope dalam menutupi `val x` di scope luar | Warning |
|
|
824
|
-
| Format | File tidak terformat | Warning |
|
|
825
|
-
| Style | Baris terlalu panjang (>120 karakter), TODO/FIXME | Info |
|
|
826
|
-
|
|
827
|
-
### Perbedaan `flux check` vs `flux lint`
|
|
1216
|
+
`flux lint` runs everything `flux check` does, plus three AST-level rules:
|
|
828
1217
|
|
|
829
|
-
| |
|
|
830
|
-
|
|
831
|
-
|
|
|
832
|
-
| Immutability |
|
|
833
|
-
|
|
|
834
|
-
| **
|
|
835
|
-
| **
|
|
836
|
-
|
|
|
837
|
-
|
|
838
|
-
|
|
1218
|
+
| Layer | Rule | Severity |
|
|
1219
|
+
|---|---|---|
|
|
1220
|
+
| Syntax | Parse errors | Error (fatal) |
|
|
1221
|
+
| Immutability | `val` reassignment | Error |
|
|
1222
|
+
| Type checker | Type mismatches, interface | Error |
|
|
1223
|
+
| **unused-var** | `val`/`var` declared but never read | Warning |
|
|
1224
|
+
| **unreachable** | Statement after `return`/`throw`/`break` | Error |
|
|
1225
|
+
| **shadow-val** | Inner `val x` hides outer `val x` | Warning |
|
|
1226
|
+
| Format | File is not formatted | Warning |
|
|
1227
|
+
| Style | Line > 120 chars, TODO/FIXME | Info |
|
|
839
1228
|
|
|
840
1229
|
```
|
|
841
1230
|
$ flux lint app.flux
|
|
842
1231
|
|
|
843
|
-
⊛ Linting: app.flux
|
|
844
|
-
|
|
845
1232
|
[E] unreachable:15:5 Unreachable code after 'return'
|
|
846
|
-
hint: Remove or move the unreachable statement
|
|
847
1233
|
15 │ val dead = 99
|
|
848
|
-
│ ^
|
|
849
1234
|
|
|
850
1235
|
[W] unused-var:8:5 'temp' is declared but never used
|
|
851
|
-
hint: Prefix with '_'
|
|
1236
|
+
hint: Prefix with '_' to silence
|
|
852
1237
|
8 │ val temp = calculate()
|
|
853
|
-
│ ^
|
|
854
1238
|
|
|
855
1239
|
[W] shadow-val:22:9 'x' shadows an outer declaration
|
|
856
|
-
hint: Rename one of the 'x' variables to avoid confusion
|
|
857
1240
|
22 │ val x = 0
|
|
858
|
-
│ ^
|
|
859
1241
|
|
|
860
1242
|
──────────────────────────────────────────────────
|
|
861
|
-
✗ app.flux — 3 issue(s)
|
|
862
|
-
```
|
|
863
|
-
|
|
864
|
-
### Cara Menonaktifkan Warning Tertentu
|
|
865
|
-
|
|
866
|
-
Prefiks nama variabel dengan `_` untuk menonaktifkan `unused-var` dan `shadow-val`:
|
|
867
|
-
|
|
868
|
-
```flux
|
|
869
|
-
val _ignored = sideEffect() // tidak dilaporkan unused
|
|
870
|
-
fn process(_unused, value): // param tidak dilaporkan unused
|
|
871
|
-
return value * 2
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
---
|
|
875
|
-
|
|
876
|
-
## Bug Fixes & Riwayat Perubahan
|
|
877
|
-
|
|
878
|
-
### v3.1.1 — Perbaikan Bug (Juni 2026)
|
|
879
|
-
|
|
880
|
-
Empat bug ditemukan melalui analisis statis dan dibuktikan dengan test sebelum diperbaiki. Semua perubahan diverifikasi dengan suite test penuh (71 test, 0 gagal).
|
|
881
|
-
|
|
882
|
-
---
|
|
883
|
-
|
|
884
|
-
#### Bug #1 — `codegen.js`: Wildcard arm pertama menghasilkan `else {` tanpa `if`
|
|
885
|
-
|
|
886
|
-
**Dampak:** `SyntaxError` runtime di JavaScript — kode tidak bisa dijalankan sama sekali.
|
|
887
|
-
|
|
888
|
-
**Penyebab:** `genMatch` di `src/codegen.js` menghasilkan `else {` untuk arm wildcard, tetapi jika wildcard adalah arm *pertama*, tidak ada `if` sebelumnya → JS tidak valid.
|
|
889
|
-
|
|
890
|
-
**Sebelum:**
|
|
891
|
-
```javascript
|
|
892
|
-
// Jika wildcard adalah arm pertama, menghasilkan:
|
|
893
|
-
else { // ← SyntaxError: tidak ada `if` sebelumnya
|
|
894
|
-
return "matched";
|
|
895
|
-
}
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
**Sesudah:** Ditambahkan flag `hasOpenIf` — arm wildcard pertama menghasilkan `if (true) {` sehingga JS tetap valid.
|
|
899
|
-
|
|
900
|
-
```flux
|
|
901
|
-
// Kode Flux ini sekarang berfungsi benar:
|
|
902
|
-
fn classify(x):
|
|
903
|
-
match x:
|
|
904
|
-
when _ -> "default"
|
|
905
|
-
when 1 -> "one"
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
---
|
|
909
|
-
|
|
910
|
-
#### Bug #2 — `stdlib.js`: `truncate` menghasilkan string terlalu panjang jika suffix ≥ len
|
|
911
|
-
|
|
912
|
-
**Dampak:** Hasil `truncate` melebihi panjang yang diminta.
|
|
913
|
-
|
|
914
|
-
**Penyebab:** `slice(0, len - suffix.length)` menghasilkan indeks negatif jika `suffix.length >= len`, yang diinterpretasi JavaScript sebagai `slice(0, negatif)` → mengembalikan karakter dari akhir string alih-alih awal.
|
|
915
|
-
|
|
916
|
-
**Sebelum:**
|
|
917
|
-
```javascript
|
|
918
|
-
return str.slice(0, len - suffix.length) + suffix;
|
|
919
|
-
// truncate("hello world", 3, "......") → hasil > 3 karakter ❌
|
|
1243
|
+
✗ app.flux — 3 issue(s)
|
|
920
1244
|
```
|
|
921
1245
|
|
|
922
|
-
|
|
923
|
-
```javascript
|
|
924
|
-
const cut = len - suffix.length;
|
|
925
|
-
if (cut <= 0) return suffix.slice(0, len);
|
|
926
|
-
return str.slice(0, cut) + suffix;
|
|
927
|
-
// truncate("hello world", 3, "......") → "..." (3 karakter) ✓
|
|
928
|
-
```
|
|
929
|
-
|
|
930
|
-
---
|
|
931
|
-
|
|
932
|
-
#### Bug #3 — `parser.js`: `(a, b, c)` tanpa `->` diam-diam membuang nilai
|
|
1246
|
+
Prefix any name with `_` to suppress `unused-var` and `shadow-val`:
|
|
933
1247
|
|
|
934
|
-
**Dampak:** Nilai di posisi 1, 2, ... dalam ekspresi kurung banyak diam-diam hilang tanpa pesan error apapun — sangat sulit di-debug.
|
|
935
|
-
|
|
936
|
-
**Penyebab:** Parser membangun `items[]` dari ekspresi berkoma di dalam `(...)`, tetapi jika tidak ada `->` (bukan lambda), ia mengembalikan `items[0]` saja.
|
|
937
|
-
|
|
938
|
-
**Sebelum:**
|
|
939
1248
|
```flux
|
|
940
|
-
val
|
|
941
|
-
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
**Sesudah:** Jika `items.length > 1` tanpa `->`, ParseError yang jelas dilempar:
|
|
945
|
-
```
|
|
946
|
-
ParseError: Unexpected comma in expression — did you mean a lambda?
|
|
947
|
-
Write (p0, p1, p2) -> expr
|
|
1249
|
+
val _ignored = sideEffect()
|
|
1250
|
+
fn process(_unused, value): return value * 2
|
|
948
1251
|
```
|
|
949
1252
|
|
|
950
1253
|
---
|
|
951
1254
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
**Dampak:** Member enum setelah nilai eksplisit mendapat indeks yang salah.
|
|
955
|
-
|
|
956
|
-
**Penyebab:** `autoIndex` hanya dimulai dari 0 dan naik +1, tanpa pernah mengambil nilai dari assignment eksplisit `member = N`.
|
|
957
|
-
|
|
958
|
-
**Sebelum:**
|
|
959
|
-
```flux
|
|
960
|
-
enum Color:
|
|
961
|
-
Red // 0 ✓
|
|
962
|
-
Green = 10 // 10 ✓
|
|
963
|
-
Blue // 2 ❌ (seharusnya 11)
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
**Sesudah:** Setelah nilai eksplisit `N`, `autoIndex` diatur ke `N` sehingga member berikutnya mendapat `N + 1`:
|
|
967
|
-
```flux
|
|
968
|
-
enum Color:
|
|
969
|
-
Red // 0 ✓
|
|
970
|
-
Green = 10 // 10 ✓
|
|
971
|
-
Blue // 11 ✓
|
|
972
|
-
```
|
|
973
|
-
|
|
974
|
-
---
|
|
975
|
-
|
|
976
|
-
#### Regresi
|
|
977
|
-
|
|
978
|
-
Semua 4 perbaikan diverifikasi tidak merusak test yang ada:
|
|
979
|
-
|
|
980
|
-
| Suite | Test | Hasil |
|
|
981
|
-
|-------|------|-------|
|
|
982
|
-
| `01_basics` — `09_ts_compat` | 53 test | ✅ Semua lulus |
|
|
983
|
-
| `10_bugfixes` (baru) | 18 test | ✅ Semua lulus |
|
|
984
|
-
| `prove_bugs.js` (bukti bug) | 7 test | ✅ Semua lulus |
|
|
985
|
-
|
|
986
|
-
---
|
|
987
|
-
|
|
988
|
-
## Arsitektur Transpiler
|
|
1255
|
+
## Transpiler Architecture
|
|
989
1256
|
|
|
990
1257
|
```
|
|
991
1258
|
Flux Source (.flux)
|
|
992
1259
|
│
|
|
993
1260
|
▼
|
|
994
|
-
[CSS Preprocessor] css`...` → string template
|
|
1261
|
+
[CSS Preprocessor] css{} / css`...` → string template
|
|
995
1262
|
│
|
|
996
1263
|
▼
|
|
997
|
-
[JSX Preprocessor] <div>...</div>
|
|
1264
|
+
[JSX Preprocessor] <div>...</div> → createElement(...)
|
|
998
1265
|
│
|
|
999
1266
|
▼
|
|
1000
|
-
[Lexer] Source
|
|
1267
|
+
[Lexer] Source → Token stream
|
|
1001
1268
|
│
|
|
1002
1269
|
▼
|
|
1003
|
-
[Parser] Tokens
|
|
1270
|
+
[Parser] Tokens → AST (Abstract Syntax Tree)
|
|
1004
1271
|
│
|
|
1005
1272
|
├──▶ [Val Checker] AST → immutability errors
|
|
1006
1273
|
│
|
|
1007
1274
|
├──▶ [Type Checker] AST → type errors & warnings
|
|
1008
1275
|
│
|
|
1009
1276
|
▼
|
|
1010
|
-
[Code Generator] AST
|
|
1277
|
+
[Code Generator] AST → JavaScript output
|
|
1011
1278
|
│
|
|
1012
1279
|
▼
|
|
1013
|
-
JavaScript (.js)
|
|
1280
|
+
JavaScript (.js) Ready to run in Node.js / browser
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
---
|
|
1284
|
+
|
|
1285
|
+
## Self-Hosting
|
|
1286
|
+
|
|
1287
|
+
Flux v3 is **100% self-hosted** — the compiler is written in Flux itself. The canonical source lives in `src/self/`:
|
|
1288
|
+
|
|
1289
|
+
```
|
|
1290
|
+
src/self/lexer.flux → compiled → src/lexer.js
|
|
1291
|
+
src/self/parser.flux → compiled → src/parser.js
|
|
1292
|
+
src/self/codegen.flux → compiled → src/codegen.js
|
|
1293
|
+
src/self/type-checker.flux → compiled → src/type-checker.js
|
|
1294
|
+
src/self/transpiler.flux → compiled → src/transpiler.js
|
|
1295
|
+
...
|
|
1014
1296
|
```
|
|
1015
1297
|
|
|
1298
|
+
Bootstrap the self-hosted compiler:
|
|
1299
|
+
|
|
1300
|
+
```bash
|
|
1301
|
+
node scripts/bootstrap.js
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
This compiles all `.flux` source files into their `.js` counterparts using the existing JS stage of the compiler.
|
|
1305
|
+
|
|
1016
1306
|
---
|
|
1017
1307
|
|
|
1018
|
-
##
|
|
1019
|
-
|
|
1020
|
-
|
|
|
1021
|
-
|
|
1022
|
-
|
|
|
1023
|
-
| Type annotations
|
|
1024
|
-
| Return type
|
|
1025
|
-
| Union types
|
|
1026
|
-
| Nullable
|
|
1027
|
-
| Pattern matching
|
|
1028
|
-
| ADT
|
|
1029
|
-
| Pipe operator
|
|
1030
|
-
| Template strings
|
|
1031
|
-
| Format
|
|
1032
|
-
|
|
|
1033
|
-
|
|
|
1034
|
-
|
|
|
1035
|
-
|
|
|
1308
|
+
## Differences from TypeScript
|
|
1309
|
+
|
|
1310
|
+
| Feature | TypeScript | Flux Lang |
|
|
1311
|
+
|---|---|---|
|
|
1312
|
+
| Syntax | C-style `{}` blocks | Indentation (Python-style) |
|
|
1313
|
+
| Type annotations | `x: string` | `x: String` |
|
|
1314
|
+
| Return type | `(): string {}` | `() -> String:` |
|
|
1315
|
+
| Union types | `string \| number` | `String \| Int` |
|
|
1316
|
+
| Nullable | `string \| null` | `String?` |
|
|
1317
|
+
| Pattern matching | none | `match / when` |
|
|
1318
|
+
| ADT | none | `type Result = Ok(v) \| Err(e)` |
|
|
1319
|
+
| Pipe operator | none | `\|>` (F# style) |
|
|
1320
|
+
| Template strings | `` `${x}` `` | `"{x}"` |
|
|
1321
|
+
| Format specs | none | `"{n:.2f}"`, `"{n:,}"` |
|
|
1322
|
+
| Decorators | `@decorator` (experimental) | `@decorator` (stable) |
|
|
1323
|
+
| Private fields | `#field` (manual) | `private var field` → `#field` |
|
|
1324
|
+
| Immutable binding | `const` | `val` |
|
|
1325
|
+
| Mutable binding | `let` | `var` |
|
|
1326
|
+
| npm interop | Yes | Yes (100% compatible) |
|
|
1327
|
+
| Output | `.js` | `.js` |
|
|
1036
1328
|
|
|
1037
1329
|
---
|
|
1038
1330
|
|
|
1039
|
-
##
|
|
1331
|
+
## Full Example — HTTP Server
|
|
1040
1332
|
|
|
1041
1333
|
```flux
|
|
1042
1334
|
import express from 'express'
|
|
@@ -1044,8 +1336,8 @@ import express from 'express'
|
|
|
1044
1336
|
type Result = Ok(data) | Err(message)
|
|
1045
1337
|
|
|
1046
1338
|
interface User:
|
|
1047
|
-
id:
|
|
1048
|
-
name:
|
|
1339
|
+
id: Int
|
|
1340
|
+
name: String
|
|
1049
1341
|
email: String
|
|
1050
1342
|
|
|
1051
1343
|
fn validateEmail(email: String) -> Bool:
|
|
@@ -1053,13 +1345,13 @@ fn validateEmail(email: String) -> Bool:
|
|
|
1053
1345
|
|
|
1054
1346
|
fn createUser(id: Int, name: String, email: String) -> Result:
|
|
1055
1347
|
if !validateEmail(email):
|
|
1056
|
-
return Err("
|
|
1348
|
+
return Err("Invalid email: {email}")
|
|
1057
1349
|
return Ok({ id, name, email })
|
|
1058
1350
|
|
|
1059
1351
|
val app = express()
|
|
1060
1352
|
app.use(express.json())
|
|
1061
1353
|
|
|
1062
|
-
var users = []
|
|
1354
|
+
var users: Array<User> = []
|
|
1063
1355
|
|
|
1064
1356
|
app.post("/users", fn(req, res):
|
|
1065
1357
|
val { id, name, email } = req.body
|
|
@@ -1078,12 +1370,38 @@ app.get("/users", fn(req, res):
|
|
|
1078
1370
|
)
|
|
1079
1371
|
|
|
1080
1372
|
app.listen(3000, fn():
|
|
1081
|
-
print("
|
|
1373
|
+
print("Flux server running on port 3000")
|
|
1082
1374
|
)
|
|
1083
1375
|
```
|
|
1084
1376
|
|
|
1085
1377
|
---
|
|
1086
1378
|
|
|
1087
|
-
##
|
|
1379
|
+
## Changelog
|
|
1380
|
+
|
|
1381
|
+
### v3.2.0 — June 2026
|
|
1382
|
+
- **Decorators** — `@name` and `@name(args)` on `fn` and `class`
|
|
1383
|
+
- **Private fields** — `private var x` → JS `#x` syntax
|
|
1384
|
+
- **+58 stdlib functions** — array extras, math, objects, strings, type checks, async, functional
|
|
1385
|
+
- **Tree-shaking** — stdlib injects only what's used in each compiled file
|
|
1386
|
+
- Transpiler version header bumped to v3.2.0
|
|
1387
|
+
|
|
1388
|
+
### v3.1.0 — June 2026
|
|
1389
|
+
- Multiline arrow block bodies
|
|
1390
|
+
- `for..of` loops, computed property keys
|
|
1391
|
+
- Class getter / setter, labeled break / continue
|
|
1392
|
+
- For-loop array destructuring
|
|
1393
|
+
- esbuild bundle (`dist/flux.cjs.js`, `dist/flux.esm.js`, `dist/flux.min.js`)
|
|
1394
|
+
- Test suite: 81 tests across 11 files
|
|
1395
|
+
|
|
1396
|
+
### v3.0.0 — June 2026
|
|
1397
|
+
- TypeScript-parity type system
|
|
1398
|
+
- Self-hosting (compiler written in Flux)
|
|
1399
|
+
- Pattern matching, ADT, enums
|
|
1400
|
+
- Pipe operator, JSX, CSS preprocessor
|
|
1401
|
+
- `flux check`, `flux lint`, `flux fmt`
|
|
1402
|
+
|
|
1403
|
+
---
|
|
1404
|
+
|
|
1405
|
+
## License
|
|
1088
1406
|
|
|
1089
|
-
MIT
|
|
1407
|
+
MIT © Flux Lang Contributors
|