dalang 0.1.0__py3-none-any.whl

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.
dalang/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Dalang (Data Analytics LANGuage).
2
+
3
+ Versi 0.1.0 berisi lexer, parser, checker, penerjemah ke Python + DuckDB, serta perintah ``dal check``, ``dal parse``, dan ``dal run``.
4
+ """
5
+
6
+ __version__ = "0.1.0"
dalang/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
dalang/ast_nodes.py ADDED
@@ -0,0 +1,565 @@
1
+ """Node AST Dalang (Tata Bahasa v0.1).
2
+
3
+ Setiap node menyimpan posisi awal (line, col) di sumber, untuk pesan error dan source map.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field, fields, is_dataclass
9
+ from typing import Optional, Union
10
+
11
+
12
+ @dataclass
13
+ class Node:
14
+ line: int = field(default=0, compare=False, repr=False, kw_only=True)
15
+ col: int = field(default=0, compare=False, repr=False, kw_only=True)
16
+
17
+
18
+ # =====================================================================
19
+ # Ekspresi
20
+ # =====================================================================
21
+
22
+ @dataclass
23
+ class Number(Node):
24
+ value: str
25
+
26
+
27
+ @dataclass
28
+ class String(Node):
29
+ value: str
30
+
31
+
32
+ @dataclass
33
+ class Bool(Node):
34
+ value: bool
35
+
36
+
37
+ @dataclass
38
+ class Missing(Node):
39
+ pass
40
+
41
+
42
+ @dataclass
43
+ class Name(Node):
44
+ name: str
45
+
46
+
47
+ @dataclass
48
+ class ListLit(Node):
49
+ items: list["Expr"]
50
+
51
+
52
+ @dataclass
53
+ class Member(Node):
54
+ obj: "Expr"
55
+ name: str
56
+ quoted: bool = False # sales."Nilai Penjualan"
57
+
58
+
59
+ @dataclass
60
+ class Arg(Node):
61
+ name: Optional[str] # argumen bernama: nama: nilai
62
+ value: "Expr"
63
+
64
+
65
+ @dataclass
66
+ class Call(Node):
67
+ func: "Expr"
68
+ args: list[Arg]
69
+
70
+
71
+ @dataclass
72
+ class Unary(Node):
73
+ op: str # "-" atau "NOT"
74
+ operand: "Expr"
75
+
76
+
77
+ @dataclass
78
+ class Binary(Node):
79
+ op: str # + - * / ^ & MOD AND OR = <> < > <= >= IN
80
+ left: "Expr"
81
+ right: "Expr"
82
+
83
+
84
+ @dataclass
85
+ class IsMissing(Node):
86
+ operand: "Expr"
87
+ negated: bool = False
88
+
89
+
90
+ # ---- pengubah pendek (aturan A2) ----
91
+
92
+ @dataclass
93
+ class Previous(Node):
94
+ expr: "Expr"
95
+ unit: str
96
+
97
+
98
+ @dataclass
99
+ class LastN(Node):
100
+ expr: "Expr"
101
+ count: str
102
+ unit: str
103
+
104
+
105
+ @dataclass
106
+ class ToDate(Node):
107
+ expr: "Expr"
108
+ unit: str
109
+
110
+
111
+ @dataclass
112
+ class IgnoreFilter(Node):
113
+ expr: "Expr"
114
+ target: "Expr"
115
+
116
+
117
+ @dataclass
118
+ class ShareOf(Node):
119
+ expr: "Expr"
120
+ target: "Expr"
121
+
122
+
123
+ @dataclass
124
+ class ForEachRow(Node):
125
+ expr: "Expr"
126
+ table: str
127
+
128
+
129
+ # ---- klausa panjang / pipeline (aturan A3) ----
130
+
131
+ @dataclass
132
+ class Coord(Node):
133
+ target: "Expr" # name_ref atau satuan waktu (Name)
134
+ value: "Expr"
135
+
136
+
137
+ @dataclass
138
+ class Where(Node):
139
+ expr: Optional["Expr"] # None bila klausa milik perintah
140
+ cond: "Expr"
141
+
142
+
143
+ @dataclass
144
+ class At(Node):
145
+ expr: Optional["Expr"]
146
+ coords: list[Coord]
147
+
148
+
149
+ @dataclass
150
+ class Select(Node):
151
+ expr: Optional["Expr"]
152
+ columns: list[str]
153
+
154
+
155
+ @dataclass
156
+ class SortBy(Node):
157
+ expr: Optional["Expr"]
158
+ key: "Expr"
159
+ descending: bool = False
160
+
161
+
162
+ # ---- bentuk khusus ----
163
+
164
+ @dataclass
165
+ class Option(Node):
166
+ name: str
167
+ value: "Expr"
168
+
169
+
170
+ @dataclass
171
+ class Load(Node):
172
+ format: str
173
+ path: str
174
+ options: list[Option]
175
+
176
+
177
+ @dataclass
178
+ class Top(Node):
179
+ count: str
180
+ table: str
181
+ key: "Expr"
182
+
183
+
184
+ @dataclass
185
+ class Rank(Node):
186
+ table: str
187
+ key: "Expr"
188
+
189
+
190
+ @dataclass
191
+ class Join(Node):
192
+ left: str
193
+ right: str
194
+ on: "Expr"
195
+
196
+
197
+ @dataclass
198
+ class Forecast(Node):
199
+ measure: str
200
+ by_unit: str
201
+ count: str
202
+ unit: str
203
+ engine: Optional[str]
204
+ options: list[Option]
205
+
206
+
207
+ @dataclass
208
+ class Regress(Node):
209
+ target: "Expr"
210
+ predictors: list["Expr"]
211
+ engine: Optional[str]
212
+ options: list[Option]
213
+
214
+
215
+ Expr = Union[
216
+ Number, String, Bool, Missing, Name, ListLit, Member, Call, Unary, Binary, IsMissing,
217
+ Previous, LastN, ToDate, IgnoreFilter, ShareOf, ForEachRow, Where, At, Select, SortBy,
218
+ Load, Top, Rank, Join, Forecast, Regress,
219
+ ]
220
+
221
+
222
+ # =====================================================================
223
+ # Pernyataan
224
+ # =====================================================================
225
+
226
+ @dataclass
227
+ class Target(Node):
228
+ name: str
229
+ column: Optional[str] = None
230
+
231
+
232
+ @dataclass
233
+ class Use(Node):
234
+ language: Optional[str] # "PYTHON", "R", atau None untuk modul .dal
235
+ path: str
236
+ alias: str
237
+
238
+
239
+ @dataclass
240
+ class Parameter(Node):
241
+ name: str
242
+ type: Optional[str]
243
+ default: Expr
244
+ options: list[Option]
245
+
246
+
247
+ @dataclass
248
+ class Let(Node):
249
+ target: Target
250
+ type: Optional[str]
251
+ value: Expr
252
+
253
+
254
+ @dataclass
255
+ class Set(Node):
256
+ target: Target
257
+ value: Expr
258
+
259
+
260
+ @dataclass
261
+ class IfBranch(Node):
262
+ cond: Expr
263
+ body: list["Stmt"]
264
+
265
+
266
+ @dataclass
267
+ class If(Node):
268
+ branches: list[IfBranch]
269
+ else_body: Optional[list["Stmt"]]
270
+ inline: bool = False
271
+
272
+
273
+ @dataclass
274
+ class For(Node):
275
+ var: str
276
+ start: Expr
277
+ end: Expr
278
+ step: Optional[Expr]
279
+ body: list["Stmt"]
280
+
281
+
282
+ @dataclass
283
+ class ForEach(Node):
284
+ var: str
285
+ iterable: Expr
286
+ body: list["Stmt"]
287
+
288
+
289
+ @dataclass
290
+ class While(Node):
291
+ cond: Expr
292
+ body: list["Stmt"]
293
+
294
+
295
+ @dataclass
296
+ class Exit(Node):
297
+ pass
298
+
299
+
300
+ @dataclass
301
+ class Continue(Node):
302
+ pass
303
+
304
+
305
+ @dataclass
306
+ class Param(Node):
307
+ name: str
308
+ type: Optional[str]
309
+ default: Optional[Expr]
310
+
311
+
312
+ @dataclass
313
+ class Function(Node):
314
+ name: str
315
+ params: list[Param]
316
+ body: list["Stmt"]
317
+
318
+
319
+ @dataclass
320
+ class Return(Node):
321
+ value: Expr
322
+
323
+
324
+ @dataclass
325
+ class Try(Node):
326
+ body: list["Stmt"]
327
+ error_name: str
328
+ handler: list["Stmt"]
329
+
330
+
331
+ @dataclass
332
+ class Fail(Node):
333
+ message: Expr
334
+
335
+
336
+ @dataclass
337
+ class Tables(Node):
338
+ names: list[str]
339
+
340
+
341
+ @dataclass
342
+ class Relate(Node):
343
+ many: Expr
344
+ one: Expr
345
+
346
+
347
+ @dataclass
348
+ class Calendar(Node):
349
+ column: Expr
350
+
351
+
352
+ @dataclass
353
+ class Measure(Node):
354
+ name: str
355
+ expr: Expr
356
+
357
+
358
+ @dataclass
359
+ class Model(Node):
360
+ name: str
361
+ items: list[Union[Tables, Relate, Calendar, Measure]]
362
+
363
+
364
+ @dataclass
365
+ class Test(Node):
366
+ name: str
367
+ expects: list[Expr]
368
+
369
+
370
+ @dataclass
371
+ class Show(Node):
372
+ values: list[Expr]
373
+ by: list[Expr]
374
+ clauses: list[Union[Where, At, Select, SortBy]]
375
+ options: list[Option]
376
+
377
+
378
+ @dataclass
379
+ class Plot(Node):
380
+ chart: Optional[str]
381
+ values: list[Expr]
382
+ by: list[Expr]
383
+ clauses: list[Union[Where, At, Select, SortBy]]
384
+ options: list[Option]
385
+
386
+
387
+ @dataclass
388
+ class Save(Node):
389
+ value: Expr
390
+ format: str
391
+ path: str
392
+ options: list[Option]
393
+
394
+
395
+ @dataclass
396
+ class Explain(Node):
397
+ expr: Expr
398
+ coords: list[Coord]
399
+ options: list[Option]
400
+
401
+
402
+ @dataclass
403
+ class Export(Node):
404
+ values: list[Expr]
405
+ by: list[Expr]
406
+ engine: str
407
+ options: list[Option]
408
+
409
+
410
+ @dataclass
411
+ class Ask(Node):
412
+ prompt: str
413
+
414
+
415
+ @dataclass
416
+ class Append(Node):
417
+ value: Expr
418
+ target: str
419
+
420
+
421
+ @dataclass
422
+ class Rename(Node):
423
+ column: Expr
424
+ new_name: str
425
+
426
+
427
+ @dataclass
428
+ class Remove(Node):
429
+ column: Expr
430
+
431
+
432
+ @dataclass
433
+ class Fill(Node):
434
+ column: Expr
435
+ value: Expr
436
+
437
+
438
+ @dataclass
439
+ class ExprStmt(Node):
440
+ expr: Expr
441
+
442
+
443
+ Stmt = Union[
444
+ Use, Parameter, Let, Set, If, For, ForEach, While, Exit, Continue, Function, Return,
445
+ Try, Fail, Model, Test, Show, Plot, Save, Explain, Export, Ask, Append, Rename, Remove,
446
+ Fill, ExprStmt,
447
+ ]
448
+
449
+
450
+ @dataclass
451
+ class Program(Node):
452
+ body: list[Stmt]
453
+
454
+
455
+ # =====================================================================
456
+ # Penulisan ulang ekspresi dengan kurung lengkap (untuk tes dan `dal parse`)
457
+ # =====================================================================
458
+
459
+ def fmt(e: object) -> str:
460
+ """Tulis ekspresi dengan kurung lengkap, sehingga cara baca parser terlihat."""
461
+ if isinstance(e, Number):
462
+ return e.value
463
+ if isinstance(e, String):
464
+ return '"' + e.value.replace('"', '""') + '"'
465
+ if isinstance(e, Bool):
466
+ return "TRUE" if e.value else "FALSE"
467
+ if isinstance(e, Missing):
468
+ return "MISSING"
469
+ if isinstance(e, Name):
470
+ return e.name
471
+ if isinstance(e, ListLit):
472
+ return "[" + ", ".join(fmt(i) for i in e.items) + "]"
473
+ if isinstance(e, Member):
474
+ name = f'"{e.name}"' if e.quoted else e.name
475
+ return f"{fmt(e.obj)}.{name}"
476
+ if isinstance(e, Call):
477
+ args = ", ".join((f"{a.name}: " if a.name else "") + fmt(a.value) for a in e.args)
478
+ return f"{fmt(e.func)}({args})"
479
+ if isinstance(e, Unary):
480
+ sep = " " if e.op == "NOT" else ""
481
+ return f"({e.op}{sep}{fmt(e.operand)})"
482
+ if isinstance(e, Binary):
483
+ return f"({fmt(e.left)} {e.op} {fmt(e.right)})"
484
+ if isinstance(e, IsMissing):
485
+ return f"({fmt(e.operand)} IS {'NOT ' if e.negated else ''}MISSING)"
486
+ if isinstance(e, Previous):
487
+ return f"({fmt(e.expr)} PREVIOUS {e.unit})"
488
+ if isinstance(e, LastN):
489
+ return f"({fmt(e.expr)} LAST {e.count} {e.unit})"
490
+ if isinstance(e, ToDate):
491
+ return f"({fmt(e.expr)} {e.unit} TO DATE)"
492
+ if isinstance(e, IgnoreFilter):
493
+ return f"({fmt(e.expr)} IGNORE FILTER {fmt(e.target)})"
494
+ if isinstance(e, ShareOf):
495
+ return f"({fmt(e.expr)} AS SHARE OF {fmt(e.target)})"
496
+ if isinstance(e, ForEachRow):
497
+ return f"({fmt(e.expr)} FOR EACH ROW OF {e.table})"
498
+ if isinstance(e, Where):
499
+ return f"({fmt(e.expr)} WHERE {fmt(e.cond)})" if e.expr else f"WHERE {fmt(e.cond)}"
500
+ if isinstance(e, At):
501
+ coords = ", ".join(f"{fmt(c.target)} = {fmt(c.value)}" for c in e.coords)
502
+ return f"({fmt(e.expr)} AT {coords})" if e.expr else f"AT {coords}"
503
+ if isinstance(e, Select):
504
+ cols = ", ".join(e.columns)
505
+ return f"({fmt(e.expr)} SELECT {cols})" if e.expr else f"SELECT {cols}"
506
+ if isinstance(e, SortBy):
507
+ tail = f"SORT BY {fmt(e.key)}" + (" DESCENDING" if e.descending else "")
508
+ return f"({fmt(e.expr)} {tail})" if e.expr else tail
509
+ if isinstance(e, Load):
510
+ return f'LOAD {e.format} "{e.path}"' + _fmt_opts(e.options)
511
+ if isinstance(e, Top):
512
+ return f"(TOP {e.count} {e.table} BY {fmt(e.key)})"
513
+ if isinstance(e, Rank):
514
+ return f"(RANK {e.table} BY {fmt(e.key)})"
515
+ if isinstance(e, Join):
516
+ return f"(JOIN {e.left}, {e.right} ON {fmt(e.on)})"
517
+ if isinstance(e, Forecast):
518
+ engine = f" USING {e.engine}" if e.engine else ""
519
+ return (f"(FORECAST {e.measure} BY {e.by_unit} NEXT {e.count} {e.unit}{engine}"
520
+ + _fmt_opts(e.options) + ")")
521
+ if isinstance(e, Regress):
522
+ engine = f" USING {e.engine}" if e.engine else ""
523
+ preds = ", ".join(fmt(p) for p in e.predictors)
524
+ return f"(REGRESS {fmt(e.target)} ON {preds}{engine}" + _fmt_opts(e.options) + ")"
525
+ if isinstance(e, Coord):
526
+ return f"{fmt(e.target)} = {fmt(e.value)}"
527
+ return f"<{type(e).__name__}>"
528
+
529
+
530
+ def _fmt_opts(options: list[Option]) -> str:
531
+ if not options:
532
+ return ""
533
+ return " WITH " + ", ".join(f"{o.name}: {fmt(o.value)}" for o in options)
534
+
535
+
536
+ def dump(node: object, indent: int = 0) -> str:
537
+ """Tampilan pohon AST yang ringkas, satu node per baris."""
538
+ pad = " " * indent
539
+ if isinstance(node, list):
540
+ return "\n".join(dump(n, indent) for n in node)
541
+ if not is_dataclass(node):
542
+ return f"{pad}{node!r}"
543
+ expr_types = Expr.__args__ # type: ignore[attr-defined]
544
+ if isinstance(node, expr_types):
545
+ return f"{pad}{fmt(node)}"
546
+ lines = [f"{pad}{type(node).__name__} (baris {node.line})"]
547
+ for f in fields(node):
548
+ if f.name in ("line", "col"):
549
+ continue
550
+ value = getattr(node, f.name)
551
+ if value is None or value == [] or value is False:
552
+ continue
553
+ if isinstance(value, list):
554
+ lines.append(f"{pad} {f.name}:")
555
+ for item in value:
556
+ lines.append(dump(item, indent + 2))
557
+ elif is_dataclass(value):
558
+ if isinstance(value, expr_types):
559
+ lines.append(f"{pad} {f.name}: {fmt(value)}")
560
+ else:
561
+ lines.append(f"{pad} {f.name}:")
562
+ lines.append(dump(value, indent + 2))
563
+ else:
564
+ lines.append(f"{pad} {f.name}: {value!r}")
565
+ return "\n".join(lines)