@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
package/README.md
ADDED
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
# Flux Lang v3.1.1 — TypeScript Parity Edition
|
|
2
|
+
|
|
3
|
+
**Flux** adalah bahasa pemrograman modern yang di-transpile ke JavaScript — menggabungkan sintaks bersih ala Python (indentasi berbasis blok), tipe statis ala TypeScript, dan ekspresi kuat ala Rust/Haskell.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
source.flux → [Lexer → Parser → TypeChecker → CodeGen] → output.js
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Instalasi & Penggunaan
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Jalankan langsung
|
|
15
|
+
node bin/flux.js run app.flux
|
|
16
|
+
|
|
17
|
+
# Compile ke JS
|
|
18
|
+
node bin/flux.js compile app.flux -o dist/app.js
|
|
19
|
+
|
|
20
|
+
# Type check (temukan kesalahan tipe sebelum runtime)
|
|
21
|
+
node bin/flux.js check app.flux
|
|
22
|
+
|
|
23
|
+
# Format kode
|
|
24
|
+
node bin/flux.js fmt app.flux
|
|
25
|
+
|
|
26
|
+
# Jalankan semua test
|
|
27
|
+
node bin/flux.js test tests/
|
|
28
|
+
|
|
29
|
+
# REPL interaktif
|
|
30
|
+
node bin/flux.js repl
|
|
31
|
+
|
|
32
|
+
# Bundle beberapa file
|
|
33
|
+
node bin/flux.js bundle main.flux -o bundle.js
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Sintaks Dasar
|
|
39
|
+
|
|
40
|
+
### Variabel
|
|
41
|
+
|
|
42
|
+
```flux
|
|
43
|
+
val x = 42 // immutable (const)
|
|
44
|
+
var count = 0 // mutable (let)
|
|
45
|
+
|
|
46
|
+
// Dengan type annotation
|
|
47
|
+
val name: String = "Flux"
|
|
48
|
+
val age: Int = 3
|
|
49
|
+
val score: Float = 9.5
|
|
50
|
+
val active: Bool = true
|
|
51
|
+
val nothing: Null = null
|
|
52
|
+
|
|
53
|
+
// Tanpa annotation — type diinfer otomatis
|
|
54
|
+
val items = [1, 2, 3] // infer: Array<Int>
|
|
55
|
+
val greeting = "Hello" // infer: String
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Print & Template Strings
|
|
59
|
+
|
|
60
|
+
```flux
|
|
61
|
+
print("Hello, World!")
|
|
62
|
+
print("Dua ditambah dua: {2 + 2}")
|
|
63
|
+
print("Pi: {3.14159:.2f}") // format spec
|
|
64
|
+
print("Besar: {n:,}") // separator ribuan
|
|
65
|
+
print("Persen: {rate:.1%}") // format persen
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Operator
|
|
69
|
+
|
|
70
|
+
```flux
|
|
71
|
+
// Aritmatika
|
|
72
|
+
val a = 10 + 3 // 13
|
|
73
|
+
val b = 10 - 3 // 7
|
|
74
|
+
val c = 10 * 3 // 30
|
|
75
|
+
val d = 10 / 3 // 3.333...
|
|
76
|
+
val e = 10 % 3 // 1
|
|
77
|
+
val f = 2 ** 10 // 1024
|
|
78
|
+
|
|
79
|
+
// Perbandingan
|
|
80
|
+
val eq = a == b // false
|
|
81
|
+
val neq = a != b // true
|
|
82
|
+
val lt = a < b // false
|
|
83
|
+
|
|
84
|
+
// Logika
|
|
85
|
+
val and_ = true && false // false
|
|
86
|
+
val or_ = true || false // true
|
|
87
|
+
val not_ = !true // false
|
|
88
|
+
|
|
89
|
+
// Nullish coalescing
|
|
90
|
+
val result = maybeNull ?? "default"
|
|
91
|
+
|
|
92
|
+
// Optional chaining
|
|
93
|
+
val len = user?.name?.length
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Fungsi
|
|
99
|
+
|
|
100
|
+
### Deklarasi Dasar
|
|
101
|
+
|
|
102
|
+
```flux
|
|
103
|
+
// Inline (satu baris)
|
|
104
|
+
fn double(x) -> x * 2
|
|
105
|
+
fn add(a, b) -> a + b
|
|
106
|
+
fn greet(name) -> "Hello, {name}!"
|
|
107
|
+
|
|
108
|
+
// Block body
|
|
109
|
+
fn factorial(n):
|
|
110
|
+
if n <= 1: return 1
|
|
111
|
+
return n * factorial(n - 1)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Type Annotations
|
|
115
|
+
|
|
116
|
+
```flux
|
|
117
|
+
// Parameter dan return type
|
|
118
|
+
fn greet(name: String) -> String:
|
|
119
|
+
return "Hello, {name}!"
|
|
120
|
+
|
|
121
|
+
fn add(a: Int, b: Int) -> Int:
|
|
122
|
+
return a + b
|
|
123
|
+
|
|
124
|
+
fn area(w: Float, h: Float) -> Float:
|
|
125
|
+
return w * h
|
|
126
|
+
|
|
127
|
+
// Parameter opsional
|
|
128
|
+
fn greetUser(name: String, title?: String) -> String:
|
|
129
|
+
val prefix = title ?? ""
|
|
130
|
+
if prefix != "":
|
|
131
|
+
return "{prefix} {name}"
|
|
132
|
+
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
|
+
|
|
144
|
+
// Inline body (tanpa return type — diinfer otomatis)
|
|
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
|
|
157
|
+
fn logMessage(msg: String) -> Void:
|
|
158
|
+
print(msg)
|
|
159
|
+
|
|
160
|
+
// Never — selalu melempar error (tidak pernah selesai)
|
|
161
|
+
fn crash(msg: String) -> Never:
|
|
162
|
+
throw new Error(msg)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Parameter Rest & Default
|
|
166
|
+
|
|
167
|
+
```flux
|
|
168
|
+
// Rest parameter
|
|
169
|
+
fn sum(...nums):
|
|
170
|
+
return nums.reduce((acc, x) -> acc + x, 0)
|
|
171
|
+
|
|
172
|
+
// Default value
|
|
173
|
+
fn repeat(str: String, times: Int = 3) -> String:
|
|
174
|
+
return str.repeat(times)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Fungsi Async
|
|
178
|
+
|
|
179
|
+
```flux
|
|
180
|
+
async fn fetchData(url: String) -> String:
|
|
181
|
+
val res = await fetch(url)
|
|
182
|
+
return await res.text()
|
|
183
|
+
|
|
184
|
+
async fn main():
|
|
185
|
+
val data = await fetchData("https://api.example.com")
|
|
186
|
+
print(data)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Higher-Order Functions & Lambda
|
|
190
|
+
|
|
191
|
+
```flux
|
|
192
|
+
// Lambda ekspresi
|
|
193
|
+
val square = x -> x * x
|
|
194
|
+
val add = (a, b) -> a + b
|
|
195
|
+
|
|
196
|
+
// Fungsi sebagai argumen
|
|
197
|
+
val doubled = [1, 2, 3].map(x -> x * 2) // [2, 4, 6]
|
|
198
|
+
val evens = [1, 2, 3, 4, 5].filter(x -> x % 2 == 0) // [2, 4]
|
|
199
|
+
val total = [1, 2, 3, 4].reduce((acc, x) -> acc + x, 0) // 10
|
|
200
|
+
|
|
201
|
+
// Pipe operator — rantai transformasi
|
|
202
|
+
val result = [1, 2, 3, 4, 5]
|
|
203
|
+
|> filter(x -> x > 2)
|
|
204
|
+
|> map(x -> x * 10)
|
|
205
|
+
|> sum
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Tipe Data (Type System)
|
|
211
|
+
|
|
212
|
+
Flux v3.0.0 memiliki sistem tipe yang diperiksa saat kompilasi (`flux check`). Tipe dihapus pada output JavaScript — sama seperti TypeScript.
|
|
213
|
+
|
|
214
|
+
### Tipe Primitif
|
|
215
|
+
|
|
216
|
+
| Tipe | Deskripsi | Contoh |
|
|
217
|
+
|-----------|------------------------------|-----------------------|
|
|
218
|
+
| `String` | Teks | `"Hello"`, `'World'` |
|
|
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 | — |
|
|
228
|
+
|
|
229
|
+
### Union Types
|
|
230
|
+
|
|
231
|
+
```flux
|
|
232
|
+
val id: Int | String = 42 // Int atau String
|
|
233
|
+
val value: Int | String = "hello" // juga valid
|
|
234
|
+
|
|
235
|
+
fn stringify(x: Int | String | Bool) -> String:
|
|
236
|
+
return "{x}"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Nullable Types
|
|
240
|
+
|
|
241
|
+
```flux
|
|
242
|
+
// String? adalah singkatan dari String | Null
|
|
243
|
+
val name: String? = null
|
|
244
|
+
val other: String? = "Flux"
|
|
245
|
+
|
|
246
|
+
fn findById(id: Int) -> String?:
|
|
247
|
+
if id > 0: return "item_{id}"
|
|
248
|
+
return null
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Generic Types
|
|
252
|
+
|
|
253
|
+
```flux
|
|
254
|
+
val names: Array<String> = ["Andi", "Budi", "Cici"]
|
|
255
|
+
val nums: Array<Int> = [1, 2, 3, 4, 5]
|
|
256
|
+
|
|
257
|
+
fn first(items: Array<String>) -> String?:
|
|
258
|
+
if items.length == 0: return null
|
|
259
|
+
return items[0]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Interface
|
|
265
|
+
|
|
266
|
+
Interface mendefinisikan kontrak yang harus dipenuhi class. `flux check` melaporkan **error** jika ada anggota yang tidak diimplementasi.
|
|
267
|
+
|
|
268
|
+
```flux
|
|
269
|
+
interface Printable:
|
|
270
|
+
fn print() -> Void
|
|
271
|
+
|
|
272
|
+
interface Shape:
|
|
273
|
+
name: String
|
|
274
|
+
fn area() -> Float
|
|
275
|
+
fn perimeter() -> Float
|
|
276
|
+
|
|
277
|
+
// Interface dengan inheritance
|
|
278
|
+
interface ColoredShape extends Shape:
|
|
279
|
+
color: String
|
|
280
|
+
fn setColor(c: String) -> Void
|
|
281
|
+
|
|
282
|
+
// Generic interface
|
|
283
|
+
interface Container<T>:
|
|
284
|
+
fn get() -> T
|
|
285
|
+
fn set(value: T) -> Void
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Class Mengimplementasi Interface
|
|
289
|
+
|
|
290
|
+
```flux
|
|
291
|
+
class Rectangle implements Shape:
|
|
292
|
+
name: String
|
|
293
|
+
width: Float
|
|
294
|
+
height: Float
|
|
295
|
+
|
|
296
|
+
fn area() -> Float:
|
|
297
|
+
return self.width * self.height
|
|
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
|
|
305
|
+
|
|
306
|
+
fn area() -> Float:
|
|
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)
|
|
313
|
+
print(r.area()) // 15
|
|
314
|
+
print(r.perimeter()) // 16
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Class
|
|
320
|
+
|
|
321
|
+
```flux
|
|
322
|
+
class Animal:
|
|
323
|
+
name: String
|
|
324
|
+
sound: String
|
|
325
|
+
|
|
326
|
+
fn speak() -> String:
|
|
327
|
+
return "{self.name} says {self.sound}!"
|
|
328
|
+
|
|
329
|
+
// Inheritance
|
|
330
|
+
class Dog extends Animal:
|
|
331
|
+
breed: String
|
|
332
|
+
|
|
333
|
+
fn info() -> String:
|
|
334
|
+
return "Dog: {self.name} ({self.breed})"
|
|
335
|
+
|
|
336
|
+
val dog = new Dog("Rex", "Woof", "Labrador")
|
|
337
|
+
print(dog.speak()) // Rex says Woof!
|
|
338
|
+
print(dog.info()) // Dog: Rex (Labrador)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Access Modifiers
|
|
342
|
+
|
|
343
|
+
```flux
|
|
344
|
+
class BankAccount:
|
|
345
|
+
private balance: Float
|
|
346
|
+
readonly owner: String
|
|
347
|
+
|
|
348
|
+
fn deposit(amount: Float) -> Void:
|
|
349
|
+
self.balance += amount
|
|
350
|
+
|
|
351
|
+
fn getBalance() -> Float:
|
|
352
|
+
return self.balance
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Algebraic Data Types (ADT)
|
|
358
|
+
|
|
359
|
+
```flux
|
|
360
|
+
// Definisi
|
|
361
|
+
type Option = Some(value) | None
|
|
362
|
+
type Result = Ok(value) | Err(message)
|
|
363
|
+
type Shape = Circle(radius) | Rectangle(w, h) | Triangle(base, height)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Pattern Matching dengan ADT
|
|
367
|
+
|
|
368
|
+
```flux
|
|
369
|
+
fn safeDivide(a: Float, b: Float) -> Result:
|
|
370
|
+
if b == 0.0: return Err("Division by zero")
|
|
371
|
+
return Ok(a / b)
|
|
372
|
+
|
|
373
|
+
val result = safeDivide(10.0, 2.0)
|
|
374
|
+
|
|
375
|
+
match result:
|
|
376
|
+
when Ok(v) -> print("Result: {v}")
|
|
377
|
+
when Err(e) -> print("Error: {e}")
|
|
378
|
+
|
|
379
|
+
// Dengan guard
|
|
380
|
+
match result:
|
|
381
|
+
when Ok(v) if v > 0 -> print("Positive: {v}")
|
|
382
|
+
when Ok(v) -> print("Non-positive: {v}")
|
|
383
|
+
when Err(e) -> print("Error: {e}")
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Enum
|
|
389
|
+
|
|
390
|
+
```flux
|
|
391
|
+
enum Direction:
|
|
392
|
+
North = 0
|
|
393
|
+
South = 1
|
|
394
|
+
East = 2
|
|
395
|
+
West = 3
|
|
396
|
+
|
|
397
|
+
enum Status:
|
|
398
|
+
Active
|
|
399
|
+
Inactive
|
|
400
|
+
Pending
|
|
401
|
+
|
|
402
|
+
print(Direction.North) // 0
|
|
403
|
+
print(Status.Active) // 0
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Kontrol Alur
|
|
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
|
|
422
|
+
|
|
423
|
+
```flux
|
|
424
|
+
// Iterasi array
|
|
425
|
+
for item in items:
|
|
426
|
+
print(item)
|
|
427
|
+
|
|
428
|
+
// Range
|
|
429
|
+
for i in 0..10:
|
|
430
|
+
print(i)
|
|
431
|
+
|
|
432
|
+
// While
|
|
433
|
+
var n = 10
|
|
434
|
+
while n > 0:
|
|
435
|
+
print(n)
|
|
436
|
+
n -= 1
|
|
437
|
+
|
|
438
|
+
// Do-while
|
|
439
|
+
do:
|
|
440
|
+
print("minimal sekali")
|
|
441
|
+
while false
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Match (Pattern Matching)
|
|
445
|
+
|
|
446
|
+
```flux
|
|
447
|
+
match nilai:
|
|
448
|
+
when 0 -> print("nol")
|
|
449
|
+
when 1..9 -> print("satu digit")
|
|
450
|
+
when 10..99 -> print("dua digit")
|
|
451
|
+
when _ -> print("lainnya")
|
|
452
|
+
|
|
453
|
+
// Match dengan guard
|
|
454
|
+
match score:
|
|
455
|
+
when s if s >= 90 -> print("A")
|
|
456
|
+
when s if s >= 80 -> print("B")
|
|
457
|
+
when s if s >= 70 -> print("C")
|
|
458
|
+
when _ -> print("D")
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Error Handling
|
|
464
|
+
|
|
465
|
+
```flux
|
|
466
|
+
// Try / Catch / Finally
|
|
467
|
+
try:
|
|
468
|
+
val data = JSON.parse(input)
|
|
469
|
+
print(data)
|
|
470
|
+
catch(e):
|
|
471
|
+
print("Error: {e.message}")
|
|
472
|
+
finally:
|
|
473
|
+
print("Selesai")
|
|
474
|
+
|
|
475
|
+
// Throw
|
|
476
|
+
fn validateAge(age: Int) -> Void:
|
|
477
|
+
if age < 0:
|
|
478
|
+
throw new Error("Usia tidak boleh negatif: {age}")
|
|
479
|
+
|
|
480
|
+
// Dengan ADT Result
|
|
481
|
+
type Result = Ok(value) | Err(message)
|
|
482
|
+
|
|
483
|
+
fn parseNumber(s: String) -> Result:
|
|
484
|
+
val n = parseInt(s, 10)
|
|
485
|
+
if isNaN(n): return Err("Bukan angka: {s}")
|
|
486
|
+
return Ok(n)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Destructuring
|
|
492
|
+
|
|
493
|
+
```flux
|
|
494
|
+
// Object destructuring
|
|
495
|
+
val person = { name: "Budi", age: 25, city: "Jakarta" }
|
|
496
|
+
val { name, age, city } = person
|
|
497
|
+
|
|
498
|
+
// Dengan alias
|
|
499
|
+
val { name: personName, age: personAge } = person
|
|
500
|
+
|
|
501
|
+
// Array destructuring
|
|
502
|
+
val [first, second, ...rest] = [1, 2, 3, 4, 5]
|
|
503
|
+
|
|
504
|
+
// Dengan default value
|
|
505
|
+
val { title = "Tanpa Judul", content } = post
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Import / Export
|
|
511
|
+
|
|
512
|
+
```flux
|
|
513
|
+
// Import default
|
|
514
|
+
import express from 'express'
|
|
515
|
+
|
|
516
|
+
// Import named
|
|
517
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
518
|
+
|
|
519
|
+
// Import namespace
|
|
520
|
+
import * as path from 'path'
|
|
521
|
+
|
|
522
|
+
// Export
|
|
523
|
+
export fn calculateTax(amount: Float, rate: Float) -> Float:
|
|
524
|
+
return amount * rate
|
|
525
|
+
|
|
526
|
+
export default fn main():
|
|
527
|
+
print("Hello from main!")
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Interop dengan npm
|
|
533
|
+
|
|
534
|
+
Flux di-transpile ke JavaScript — semua package npm dapat digunakan langsung:
|
|
535
|
+
|
|
536
|
+
```flux
|
|
537
|
+
import express from 'express'
|
|
538
|
+
import { readFileSync } from 'fs'
|
|
539
|
+
|
|
540
|
+
val app = express()
|
|
541
|
+
|
|
542
|
+
app.get("/", fn(req, res):
|
|
543
|
+
res.json({ status: "ok", lang: "Flux" })
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
app.listen(3000, fn():
|
|
547
|
+
print("Server Flux berjalan di port 3000")
|
|
548
|
+
)
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## JSX (React)
|
|
554
|
+
|
|
555
|
+
```flux
|
|
556
|
+
// Server-side JSX
|
|
557
|
+
val heading = <h1 class="title">Hello Flux!</h1>
|
|
558
|
+
val card = <div class="card">
|
|
559
|
+
<h2>{title}</h2>
|
|
560
|
+
<p>{description}</p>
|
|
561
|
+
</div>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## CSS Preprocessor
|
|
567
|
+
|
|
568
|
+
```flux
|
|
569
|
+
val styles = css`
|
|
570
|
+
.button {
|
|
571
|
+
background: var(--primary);
|
|
572
|
+
border-radius: 8px;
|
|
573
|
+
padding: 8px 16px;
|
|
574
|
+
|
|
575
|
+
&:hover {
|
|
576
|
+
opacity: 0.9;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
`
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## Perintah CLI
|
|
585
|
+
|
|
586
|
+
| Perintah | Deskripsi |
|
|
587
|
+
|---------------------------------|------------------------------------------------------|
|
|
588
|
+
| `flux init [nama]` | Buat project Flux baru |
|
|
589
|
+
| `flux compile file.flux` | Compile ke JavaScript |
|
|
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
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
--out, -o <file> File output
|
|
607
|
+
--sourcemap, -m Hasilkan source map (.js.map)
|
|
608
|
+
--stdout Cetak output ke terminal
|
|
609
|
+
--no-color Nonaktifkan warna
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Sistem Tipe — TypeScript Parity
|
|
615
|
+
|
|
616
|
+
Flux v3.1.0 memiliki sistem tipe yang setara dengan TypeScript. Berikut fitur lengkapnya:
|
|
617
|
+
|
|
618
|
+
### Intersection Types (`A & B`)
|
|
619
|
+
|
|
620
|
+
```flux
|
|
621
|
+
interface Named:
|
|
622
|
+
name: String
|
|
623
|
+
|
|
624
|
+
interface Aged:
|
|
625
|
+
age: Int
|
|
626
|
+
|
|
627
|
+
// Intersection: class harus memenuhi SEMUA interface
|
|
628
|
+
class Person implements Named, Aged:
|
|
629
|
+
name: String
|
|
630
|
+
age: Int
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Tuple Types
|
|
634
|
+
|
|
635
|
+
```flux
|
|
636
|
+
fn getCoord() -> [Float, Float]:
|
|
637
|
+
return [3.14, 2.71]
|
|
638
|
+
|
|
639
|
+
fn parseKV(pair: String) -> [String, String]:
|
|
640
|
+
val parts = pair.split("=")
|
|
641
|
+
return [parts[0], parts[1]]
|
|
642
|
+
|
|
643
|
+
val [lat, lng] = getCoord() // destructure tuple
|
|
644
|
+
val [key, val_] = parseKV("a=b")
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Inline Object Types
|
|
648
|
+
|
|
649
|
+
```flux
|
|
650
|
+
fn makePoint(x: Float, y: Float) -> { x: Float, y: Float }:
|
|
651
|
+
return { x, y }
|
|
652
|
+
|
|
653
|
+
fn distance(p1: { x: Float, y: Float }, p2: { x: Float, y: Float }) -> Float:
|
|
654
|
+
val dx = p1.x - p2.x
|
|
655
|
+
val dy = p1.y - p2.y
|
|
656
|
+
return Math.sqrt(dx * dx + dy * dy)
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### Structural Typing
|
|
660
|
+
|
|
661
|
+
```flux
|
|
662
|
+
interface Printable:
|
|
663
|
+
fn toString() -> String
|
|
664
|
+
|
|
665
|
+
// Tidak perlu implements jika shape cocok (duck typing)
|
|
666
|
+
class Config implements Printable:
|
|
667
|
+
key: String
|
|
668
|
+
fn toString() -> String -> "Config({self.key})"
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### Type Narrowing via Control Flow
|
|
672
|
+
|
|
673
|
+
```flux
|
|
674
|
+
fn getUser(id: Int) -> String?:
|
|
675
|
+
if id > 0: return "User_{id}"
|
|
676
|
+
return null
|
|
677
|
+
|
|
678
|
+
val user = getUser(1)
|
|
679
|
+
|
|
680
|
+
// Flux mengecilkan tipe di dalam blok null-check
|
|
681
|
+
if user != null:
|
|
682
|
+
print(user.toUpperCase()) // OK — user adalah String di sini, bukan String?
|
|
683
|
+
|
|
684
|
+
// typeof narrowing
|
|
685
|
+
fn process(x: Int | String | Bool) -> String:
|
|
686
|
+
if typeof x == "number": return "num: {x}"
|
|
687
|
+
if typeof x == "string": return "str: {x}"
|
|
688
|
+
return "bool: {x}"
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Function Types
|
|
692
|
+
|
|
693
|
+
```flux
|
|
694
|
+
// Anotasi tipe fungsi sebagai parameter
|
|
695
|
+
fn mapInts(arr: Array<Int>, f: fn(Int) -> Int) -> Array<Int>:
|
|
696
|
+
return arr.map(f)
|
|
697
|
+
|
|
698
|
+
fn double(x: Int) -> Int:
|
|
699
|
+
return x * 2
|
|
700
|
+
|
|
701
|
+
val results = mapInts([1, 2, 3], double)
|
|
702
|
+
|
|
703
|
+
// Tipe fungsi di interface
|
|
704
|
+
interface Handler:
|
|
705
|
+
fn handle(req: String) -> String
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### `keyof` & `typeof` di Tipe
|
|
709
|
+
|
|
710
|
+
```flux
|
|
711
|
+
// keyof T — union dari semua key interface
|
|
712
|
+
fn getField(key: keyof Config) -> String:
|
|
713
|
+
return key
|
|
714
|
+
|
|
715
|
+
// typeof x — tipe dari variabel
|
|
716
|
+
val config = new Config("host", "localhost")
|
|
717
|
+
fn cloneConfig(src: typeof config) -> typeof config:
|
|
718
|
+
return new Config(src.key, src.value)
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Utility Types
|
|
722
|
+
|
|
723
|
+
```flux
|
|
724
|
+
// Semua utility type TypeScript tersedia:
|
|
725
|
+
fn setPartial(opts: Partial<Config>) -> Void:
|
|
726
|
+
// semua field optional
|
|
727
|
+
pass
|
|
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>
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Index Signatures
|
|
751
|
+
|
|
752
|
+
```flux
|
|
753
|
+
// { [key: String]: T } — map arbitrary keys to type
|
|
754
|
+
fn buildRegistry() -> { [key: String]: String }:
|
|
755
|
+
return { name: "Flux", version: "3.1.0" }
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
### Generic Type Parameters di ADT
|
|
759
|
+
|
|
760
|
+
```flux
|
|
761
|
+
// Type parameter <T> di ADT declarations
|
|
762
|
+
type Result<T> = Ok(value) | Err(message)
|
|
763
|
+
type Option<T> = Some(value) | None
|
|
764
|
+
type Either<L, R> = Left(value) | Right(value)
|
|
765
|
+
|
|
766
|
+
fn safeDivide(a: Float, b: Float) -> Result:
|
|
767
|
+
if b == 0.0: return Err("Division by zero")
|
|
768
|
+
return Ok(a / b)
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
## `flux check` — Type Checker
|
|
774
|
+
|
|
775
|
+
Perintah `flux check` menjalankan tiga lapisan:
|
|
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
|
|
780
|
+
|
|
781
|
+
### Contoh Output
|
|
782
|
+
|
|
783
|
+
```
|
|
784
|
+
$ flux check app.flux
|
|
785
|
+
|
|
786
|
+
✗ app.flux: 2 type error(s)
|
|
787
|
+
|
|
788
|
+
[TypeError]:12:5 Type 'Int' is not assignable to 'name: String'
|
|
789
|
+
hint: Change the value to match type 'String', or update the annotation
|
|
790
|
+
12 │ val name: String = 42
|
|
791
|
+
│ ^
|
|
792
|
+
|
|
793
|
+
[TypeError]:28:1 Class 'Cat' does not implement method 'area()' required by interface 'Shape'
|
|
794
|
+
hint: Add 'fn area()' to the class
|
|
795
|
+
|
|
796
|
+
✗ app.flux — 2 type errors
|
|
797
|
+
Functions: 5 | Classes: 2 | JS output: 47 lines
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
### Hal yang Diperiksa
|
|
801
|
+
|
|
802
|
+
| Pemeriksaan | Contoh yang ditangkap |
|
|
803
|
+
|-------------------------------|-----------------------------------------------------------|
|
|
804
|
+
| Mismatch tipe variabel | `val name: String = 42` → error |
|
|
805
|
+
| Return type mismatch | `fn f() -> Int: return "hello"` → error |
|
|
806
|
+
| Interface tidak diimplementasi| class tidak punya method yang diwajibkan interface |
|
|
807
|
+
| Val reassignment | `val x = 1; x = 2` → error |
|
|
808
|
+
| Nullable / union assignability| `val x: Int = null` → error |
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
## `flux lint` — Linter
|
|
813
|
+
|
|
814
|
+
Perintah `flux lint` menjalankan **semua** lapisan pemeriksaan `flux check`, ditambah tiga aturan AST-level yang menangkap masalah logika tanpa melihat tipe:
|
|
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`
|
|
828
|
+
|
|
829
|
+
| | `flux check` | `flux lint` |
|
|
830
|
+
|-|---|---|
|
|
831
|
+
| Type errors | ✓ | ✓ |
|
|
832
|
+
| Immutability | ✓ | ✓ |
|
|
833
|
+
| **Unused variables** | ✗ | ✓ |
|
|
834
|
+
| **Unreachable code** | ✗ | ✓ |
|
|
835
|
+
| **Variable shadowing** | ✗ | ✓ |
|
|
836
|
+
| Format check | ✗ | ✓ |
|
|
837
|
+
|
|
838
|
+
### Contoh Output
|
|
839
|
+
|
|
840
|
+
```
|
|
841
|
+
$ flux lint app.flux
|
|
842
|
+
|
|
843
|
+
⊛ Linting: app.flux
|
|
844
|
+
|
|
845
|
+
[E] unreachable:15:5 Unreachable code after 'return'
|
|
846
|
+
hint: Remove or move the unreachable statement
|
|
847
|
+
15 │ val dead = 99
|
|
848
|
+
│ ^
|
|
849
|
+
|
|
850
|
+
[W] unused-var:8:5 'temp' is declared but never used
|
|
851
|
+
hint: Prefix with '_' (e.g. '_temp') to silence this warning, or remove the declaration
|
|
852
|
+
8 │ val temp = calculate()
|
|
853
|
+
│ ^
|
|
854
|
+
|
|
855
|
+
[W] shadow-val:22:9 'x' shadows an outer declaration
|
|
856
|
+
hint: Rename one of the 'x' variables to avoid confusion
|
|
857
|
+
22 │ val x = 0
|
|
858
|
+
│ ^
|
|
859
|
+
|
|
860
|
+
──────────────────────────────────────────────────
|
|
861
|
+
✗ app.flux — 3 issue(s) (45 lines)
|
|
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 ❌
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
**Sesudah:** Ditambahkan guard — jika `suffix.length >= len`, kembalikan `suffix.slice(0, len)`:
|
|
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
|
|
933
|
+
|
|
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
|
+
```flux
|
|
940
|
+
val x = (10, 20, 30)
|
|
941
|
+
// Dikompilasi menjadi: const x = 10; ← 20 dan 30 hilang diam-diam
|
|
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
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
---
|
|
951
|
+
|
|
952
|
+
#### Bug #4 — `parser.js`: Auto-index enum tidak direset setelah nilai eksplisit
|
|
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
|
|
989
|
+
|
|
990
|
+
```
|
|
991
|
+
Flux Source (.flux)
|
|
992
|
+
│
|
|
993
|
+
▼
|
|
994
|
+
[CSS Preprocessor] css`...` → string template
|
|
995
|
+
│
|
|
996
|
+
▼
|
|
997
|
+
[JSX Preprocessor] <div>...</div> → createElement(...)
|
|
998
|
+
│
|
|
999
|
+
▼
|
|
1000
|
+
[Lexer] Source → Token stream
|
|
1001
|
+
│
|
|
1002
|
+
▼
|
|
1003
|
+
[Parser] Tokens → AST (Abstract Syntax Tree)
|
|
1004
|
+
│
|
|
1005
|
+
├──▶ [Val Checker] AST → immutability errors
|
|
1006
|
+
│
|
|
1007
|
+
├──▶ [Type Checker] AST → type errors & warnings
|
|
1008
|
+
│
|
|
1009
|
+
▼
|
|
1010
|
+
[Code Generator] AST → JavaScript output
|
|
1011
|
+
│
|
|
1012
|
+
▼
|
|
1013
|
+
JavaScript (.js) Siap dijalankan di Node.js / browser
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
---
|
|
1017
|
+
|
|
1018
|
+
## Perbedaan dengan TypeScript
|
|
1019
|
+
|
|
1020
|
+
| Fitur | TypeScript | Flux Lang |
|
|
1021
|
+
|--------------------------|--------------------|---------------------------------|
|
|
1022
|
+
| Sintaks | C-style `{}` | Indentasi (Python-style) |
|
|
1023
|
+
| Type annotations | `x: string` | `x: String` |
|
|
1024
|
+
| Return type | `(): string {}` | `() -> String:` |
|
|
1025
|
+
| Union types | `string \| number` | `String \| Int` |
|
|
1026
|
+
| Nullable | `string \| null` | `String?` |
|
|
1027
|
+
| Pattern matching | Tidak ada | `match / when` |
|
|
1028
|
+
| ADT | Tidak ada | `type Result = Ok(v) \| Err(e)` |
|
|
1029
|
+
| Pipe operator | Tidak ada | `\|>` (F# style) |
|
|
1030
|
+
| Template strings | `` `${x}` `` | `"{x}"` |
|
|
1031
|
+
| Format spec | Tidak ada | `"{n:.2f}"`, `"{n:,}"` |
|
|
1032
|
+
| Val (immutable) | `const` | `val` |
|
|
1033
|
+
| Var (mutable) | `let` | `var` |
|
|
1034
|
+
| npm interop | Ya | Ya (100% kompatibel) |
|
|
1035
|
+
| Transpiler output | `.js` | `.js` |
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
## Contoh Lengkap — Server HTTP
|
|
1040
|
+
|
|
1041
|
+
```flux
|
|
1042
|
+
import express from 'express'
|
|
1043
|
+
|
|
1044
|
+
type Result = Ok(data) | Err(message)
|
|
1045
|
+
|
|
1046
|
+
interface User:
|
|
1047
|
+
id: Int
|
|
1048
|
+
name: String
|
|
1049
|
+
email: String
|
|
1050
|
+
|
|
1051
|
+
fn validateEmail(email: String) -> Bool:
|
|
1052
|
+
return email.includes("@")
|
|
1053
|
+
|
|
1054
|
+
fn createUser(id: Int, name: String, email: String) -> Result:
|
|
1055
|
+
if !validateEmail(email):
|
|
1056
|
+
return Err("Email tidak valid: {email}")
|
|
1057
|
+
return Ok({ id, name, email })
|
|
1058
|
+
|
|
1059
|
+
val app = express()
|
|
1060
|
+
app.use(express.json())
|
|
1061
|
+
|
|
1062
|
+
var users = []
|
|
1063
|
+
|
|
1064
|
+
app.post("/users", fn(req, res):
|
|
1065
|
+
val { id, name, email } = req.body
|
|
1066
|
+
val result = createUser(id, name, email)
|
|
1067
|
+
|
|
1068
|
+
match result:
|
|
1069
|
+
when Ok(user):
|
|
1070
|
+
users.push(user)
|
|
1071
|
+
res.json({ success: true, user })
|
|
1072
|
+
when Err(msg):
|
|
1073
|
+
res.status(400).json({ error: msg })
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
app.get("/users", fn(req, res):
|
|
1077
|
+
res.json({ users, count: users.length })
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
app.listen(3000, fn():
|
|
1081
|
+
print("Server Flux berjalan di port 3000")
|
|
1082
|
+
)
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
---
|
|
1086
|
+
|
|
1087
|
+
## Lisensi
|
|
1088
|
+
|
|
1089
|
+
MIT — Flux Lang v3.0.0
|