gelang 0.1.0

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +535 -0
  4. package/bin/ge.js +112 -0
  5. package/package.json +62 -0
  6. package/python/pyeffic/__init__.py +9 -0
  7. package/python/pyeffic/__main__.py +6 -0
  8. package/python/pyeffic/analyzer.py +464 -0
  9. package/python/pyeffic/apisurface.py +238 -0
  10. package/python/pyeffic/autoselect.py +327 -0
  11. package/python/pyeffic/backends.py +87 -0
  12. package/python/pyeffic/bench.py +233 -0
  13. package/python/pyeffic/cli.py +184 -0
  14. package/python/pyeffic/compiler.py +421 -0
  15. package/python/pyeffic/config.py +383 -0
  16. package/python/pyeffic/dartgen.py +441 -0
  17. package/python/pyeffic/deploy.py +586 -0
  18. package/python/pyeffic/diagnostics.py +194 -0
  19. package/python/pyeffic/difftest.py +424 -0
  20. package/python/pyeffic/downloader.py +307 -0
  21. package/python/pyeffic/emitters/__init__.py +11 -0
  22. package/python/pyeffic/emitters/base.py +2359 -0
  23. package/python/pyeffic/emitters/cpp.py +266 -0
  24. package/python/pyeffic/emitters/csharp.py +342 -0
  25. package/python/pyeffic/emitters/dart.py +349 -0
  26. package/python/pyeffic/emitters/go.py +388 -0
  27. package/python/pyeffic/emitters/kotlin.py +314 -0
  28. package/python/pyeffic/emitters/rust.py +314 -0
  29. package/python/pyeffic/emitters/zig.py +411 -0
  30. package/python/pyeffic/ffi.py +49 -0
  31. package/python/pyeffic/frontends/__init__.py +94 -0
  32. package/python/pyeffic/frontends/hybrid.py +709 -0
  33. package/python/pyeffic/frontends/typescript.py +965 -0
  34. package/python/pyeffic/ge_cli.py +1148 -0
  35. package/python/pyeffic/golden.py +348 -0
  36. package/python/pyeffic/idents.py +206 -0
  37. package/python/pyeffic/modules.py +220 -0
  38. package/python/pyeffic/packer.py +222 -0
  39. package/python/pyeffic/pipeline.py +797 -0
  40. package/python/pyeffic/reactgen.py +966 -0
  41. package/python/pyeffic/researcher.py +177 -0
  42. package/python/pyeffic/scaffold.py +397 -0
  43. package/python/pyeffic/stdlib.py +246 -0
  44. package/python/pyeffic/styling.py +220 -0
  45. package/python/pyeffic/templates/desktop_gui/README.md +106 -0
  46. package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
  47. package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
  48. package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
  49. package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
  50. package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
  51. package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
  52. package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
  53. package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
  54. package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
  55. package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
  56. package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
  57. package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
  58. package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
  59. package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
  60. package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
  61. package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
  62. package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
  63. package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
  64. package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
  65. package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
  66. package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
  67. package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
  68. package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
  69. package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
  70. package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
  71. package/python/pyeffic/templates/web_react/README.md +115 -0
  72. package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
  73. package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
  74. package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
  75. package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
  76. package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
  77. package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
  78. package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
  79. package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
  80. package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
  81. package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
  82. package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
  83. package/python/pyeffic/templates/web_react/ge.toml +23 -0
  84. package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
  85. package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
  86. package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
  87. package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
  88. package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
  89. package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
  90. package/python/pyeffic/ts2py.py +657 -0
  91. package/python/pyeffic/typecheck.py +232 -0
  92. package/python/pyeffic/ui.py +154 -0
  93. package/python/pyeffic/ui_dsl.py +618 -0
  94. package/python/pyeffic/widgets.py +87 -0
  95. package/scripts/README.md +42 -0
  96. package/scripts/check-toolchains.py +85 -0
@@ -0,0 +1,349 @@
1
+ """Dart emitter: transpile Python functions to Dart for the @dart backend.
2
+
3
+ @dart functions stay in Dart (Flutter's language) instead of being compiled to
4
+ native Rust/C++. They run in Flutter's event loop and have direct access to
5
+ Flutter widgets, async, and state management.
6
+
7
+ Supported subset (same as Rust/C++ emitters):
8
+ - Functions with typed params and return types
9
+ - if/else, for (range), while
10
+ - Arithmetic, comparison, logical operators
11
+ - List literals, indexing, .length, .add()
12
+ - print() → print()
13
+ - Integer and float arithmetic
14
+
15
+ Type mappings:
16
+ int → int, float → double, bool → bool, str → String
17
+ list → List<int> (default), None → void
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import ast
22
+ from ..analyzer import FuncUnit
23
+
24
+ DART_TYPES = {
25
+ "int": "int",
26
+ "float": "double",
27
+ "bool": "bool",
28
+ "str": "String",
29
+ "None": "void",
30
+ "list": "List<int>",
31
+ }
32
+
33
+
34
+ class DartEmitter:
35
+ """Transpile Python function AST to Dart source code."""
36
+
37
+ def __init__(self):
38
+ self.lines: list[str] = []
39
+ self.indent_lvl = 0
40
+ self.var_types: dict[str, str] = {}
41
+
42
+ def emit_function(self, unit: FuncUnit) -> str:
43
+ """Emit a single function as Dart source."""
44
+ self.lines = []
45
+ self.indent_lvl = 1
46
+ self.var_types = {}
47
+
48
+ # seed param types
49
+ for pname, ptype in unit.params:
50
+ self.var_types[pname] = ptype
51
+
52
+ # build signature
53
+ params = ", ".join(
54
+ f"{DART_TYPES.get(ptype, 'double')} {pname}"
55
+ for pname, ptype in unit.params
56
+ )
57
+ ret_type = DART_TYPES.get(unit.ret_type, "void")
58
+ sig = f"{ret_type} {unit.name}({params}) {{"
59
+
60
+ # emit body
61
+ for stmt in unit.body.body: # type: ignore[attr-defined]
62
+ self.stmt(stmt)
63
+
64
+ body = "\n".join(self.lines)
65
+ return f"{sig}\n{body}\n}}\n"
66
+
67
+ def stmt(self, node: ast.AST) -> None:
68
+ ind = " " * self.indent_lvl
69
+ if isinstance(node, ast.Return):
70
+ if node.value is None:
71
+ self.lines.append(f"{ind}return;")
72
+ else:
73
+ self.lines.append(f"{ind}return {self.expr(node.value)};")
74
+ elif isinstance(node, ast.Assign):
75
+ target = node.targets[0]
76
+ val = self.expr(node.value)
77
+ if isinstance(target, ast.Name):
78
+ t = self.infer_type(node.value)
79
+ self.var_types[target.id] = t
80
+ dt = DART_TYPES.get(t, "double")
81
+ self.lines.append(f"{ind}{dt} {target.id} = {val};")
82
+ elif isinstance(target, ast.Subscript):
83
+ self.lines.append(f"{ind}{self.expr(target)} = {val};")
84
+ elif isinstance(node, ast.AugAssign):
85
+ target = self.expr(node.target)
86
+ op = self.binop_symbol(node.op)
87
+ self.lines.append(f"{ind}{target} {op}= {self.expr(node.value)};")
88
+ elif isinstance(node, ast.AnnAssign):
89
+ if node.value is None:
90
+ if isinstance(node.target, ast.Name):
91
+ t = self._ann_type(node.annotation)
92
+ self.var_types[node.target.id] = t
93
+ return
94
+ target = node.target.id if isinstance(node.target, ast.Name) else self.expr(node.target)
95
+ t = self._ann_type(node.annotation) if isinstance(node.target, ast.Name) else self.infer_type(node.value)
96
+ if isinstance(node.target, ast.Name):
97
+ self.var_types[target] = t
98
+ dt = DART_TYPES.get(t, "double")
99
+ self.lines.append(f"{ind}{dt} {target} = {self.expr(node.value)};")
100
+ elif isinstance(node, ast.If):
101
+ self.lines.append(f"{ind}if ({self.expr(node.test)}) {{")
102
+ self.indent_lvl += 1
103
+ for s in node.body:
104
+ self.stmt(s)
105
+ self.indent_lvl -= 1
106
+ if node.orelse:
107
+ if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If):
108
+ self.lines.append(f"{ind}}} else {self._elif(node.orelse[0])}")
109
+ else:
110
+ self.lines.append(f"{ind}}} else {{")
111
+ self.indent_lvl += 1
112
+ for s in node.orelse:
113
+ self.stmt(s)
114
+ self.indent_lvl -= 1
115
+ self.lines.append(f"{ind}}}")
116
+ else:
117
+ self.lines.append(f"{ind}}}")
118
+ elif isinstance(node, ast.For):
119
+ self.for_loop(node, ind)
120
+ elif isinstance(node, ast.While):
121
+ self.lines.append(f"{ind}while ({self.expr(node.test)}) {{")
122
+ self.indent_lvl += 1
123
+ for s in node.body:
124
+ self.stmt(s)
125
+ self.indent_lvl -= 1
126
+ self.lines.append(f"{ind}}}")
127
+ elif isinstance(node, ast.Expr):
128
+ if isinstance(node.value, ast.Call):
129
+ self.lines.append(f"{ind}{self.expr(node.value)};")
130
+ elif isinstance(node, ast.Pass):
131
+ pass
132
+ else:
133
+ self.lines.append(f"{ind}// unsupported stmt: {type(node).__name__}")
134
+
135
+ def _elif(self, node: ast.If) -> str:
136
+ head = f"if ({self.expr(node.test)}) {{"
137
+ save = self.lines
138
+ self.lines = []
139
+ self.indent_lvl += 1
140
+ for s in node.body:
141
+ self.stmt(s)
142
+ self.indent_lvl -= 1
143
+ body = "\n".join(self.lines)
144
+ self.lines = save
145
+ tail = ""
146
+ if node.orelse:
147
+ if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If):
148
+ tail = " } else " + self._elif(node.orelse[0])
149
+ else:
150
+ save2 = self.lines
151
+ self.lines = []
152
+ self.indent_lvl += 1
153
+ for s in node.orelse:
154
+ self.stmt(s)
155
+ self.indent_lvl -= 1
156
+ tail = " } else {\n" + "\n".join(self.lines) + "\n" + " " * self.indent_lvl + "}"
157
+ self.lines = save2
158
+ else:
159
+ tail = "\n" + " " * self.indent_lvl + "}"
160
+ return head + "\n" + body + tail
161
+
162
+ def for_loop(self, node: ast.For, ind: str) -> None:
163
+ var = node.target.id if isinstance(node.target, ast.Name) else "_"
164
+ it = node.iter
165
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
166
+ args = it.args
167
+ if len(args) == 1:
168
+ lo, hi = "0", self.expr(args[0])
169
+ elif len(args) == 2:
170
+ lo, hi = self.expr(args[0]), self.expr(args[1])
171
+ else:
172
+ lo, hi = self.expr(args[0]), self.expr(args[1])
173
+ self.var_types[var] = "int"
174
+ self.lines.append(f"{ind}for (int {var} = {lo}; {var} < {hi}; {var}++) {{")
175
+ else:
176
+ iter_s = self.expr(it)
177
+ self.var_types[var] = "int"
178
+ self.lines.append(f"{ind}for (int {var} in {iter_s}) {{")
179
+ self.indent_lvl += 1
180
+ for s in node.body:
181
+ self.stmt(s)
182
+ self.indent_lvl -= 1
183
+ self.lines.append(f"{ind}}}")
184
+
185
+ def expr(self, node: ast.AST) -> str:
186
+ if isinstance(node, ast.Constant):
187
+ v = node.value
188
+ if isinstance(v, bool):
189
+ return "true" if v else "false"
190
+ if isinstance(v, int):
191
+ return str(v)
192
+ if isinstance(v, float):
193
+ return repr(v)
194
+ if isinstance(v, str):
195
+ return "'" + v.replace("'", "\\'") + "'"
196
+ if v is None:
197
+ return "null"
198
+ if isinstance(node, ast.Name):
199
+ return node.id
200
+ if isinstance(node, ast.BinOp):
201
+ left = self.expr(node.left)
202
+ right = self.expr(node.right)
203
+ if isinstance(node.op, ast.FloorDiv):
204
+ return f"({left} ~/ {right})"
205
+ if isinstance(node.op, ast.Div):
206
+ return f"({left} / {right})"
207
+ if isinstance(node.op, ast.Pow):
208
+ return f"pow({left}, {right})"
209
+ return f"({left} {self.binop_symbol(node.op)} {right})"
210
+ if isinstance(node, ast.UnaryOp):
211
+ operand = self.expr(node.operand)
212
+ if isinstance(node.op, ast.USub):
213
+ return f"(-{operand})"
214
+ if isinstance(node.op, ast.Not):
215
+ return f"(!{operand})"
216
+ if isinstance(node, ast.BoolOp):
217
+ op = "&&" if isinstance(node.op, ast.And) else "||"
218
+ parts = [self.expr(v) for v in node.values]
219
+ return "(" + f" {op} ".join(parts) + ")"
220
+ if isinstance(node, ast.Compare):
221
+ left = self.expr(node.left)
222
+ parts = []
223
+ cur = left
224
+ for op, comp in zip(node.ops, node.comparators):
225
+ sym = self.cmp_symbol(op)
226
+ right = self.expr(comp)
227
+ parts.append(f"({cur} {sym} {right})")
228
+ cur = right
229
+ return "(" + " && ".join(parts) + ")"
230
+ if isinstance(node, ast.Call):
231
+ return self.call(node)
232
+ if isinstance(node, ast.Subscript):
233
+ base = self.expr(node.value)
234
+ if isinstance(node.slice, ast.Slice):
235
+ sl = node.slice
236
+ start = self.expr(sl.lower) if sl.lower else "0"
237
+ stop = self.expr(sl.upper) if sl.upper else None
238
+ if stop:
239
+ return f"{base}.substring({start}, {stop})"
240
+ return f"{base}.substring({start})"
241
+ idx = self.expr(node.slice)
242
+ return f"{base}[{idx}]"
243
+ if isinstance(node, ast.List):
244
+ elems = ", ".join(self.expr(e) for e in node.elts)
245
+ return f"[{elems}]"
246
+ if isinstance(node, ast.JoinedStr):
247
+ # Dart string interpolation: "text ${expr} text"
248
+ parts = []
249
+ for val in node.values:
250
+ if isinstance(val, ast.Constant) and isinstance(val.value, str):
251
+ parts.append(val.value.replace("$", "\\$").replace("'", "\\'"))
252
+ elif isinstance(val, ast.FormattedValue):
253
+ parts.append("${" + self.expr(val.value) + "}")
254
+ return "'" + "".join(parts) + "'"
255
+ if isinstance(node, ast.Attribute):
256
+ base = self.expr(node.value)
257
+ return f"{base}.{node.attr}"
258
+ return f"/*unsupported {type(node).__name__}*/"
259
+
260
+ def call(self, node: ast.Call) -> str:
261
+ fname = getattr(node.func, "id", None)
262
+ if fname == "print":
263
+ args = ", ".join(self.expr(a) for a in node.args)
264
+ return f"print({args})"
265
+ if fname == "len":
266
+ return f"{self.expr(node.args[0])}.length"
267
+ if fname == "abs":
268
+ return f"{self.expr(node.args[0])}.abs()"
269
+ if fname == "min":
270
+ args = ", ".join(self.expr(a) for a in node.args)
271
+ return f"min({args})"
272
+ if fname == "max":
273
+ args = ", ".join(self.expr(a) for a in node.args)
274
+ return f"max({args})"
275
+ if fname == "int":
276
+ return f"{self.expr(node.args[0])}.toInt()"
277
+ if fname == "float":
278
+ return f"{self.expr(node.args[0])}.toDouble()"
279
+ if isinstance(node.func, ast.Attribute) and node.func.attr == "append":
280
+ base = self.expr(node.func.value)
281
+ return f"{base}.add({self.expr(node.args[0])})"
282
+ args = ", ".join(self.expr(a) for a in node.args)
283
+ return f"{fname}({args})"
284
+
285
+ def _ann_type(self, node: ast.AST) -> str:
286
+ if isinstance(node, ast.Name):
287
+ t = node.id
288
+ return t if t in ("int", "float", "bool", "str", "list") else "float"
289
+ if isinstance(node, ast.Subscript):
290
+ return "list"
291
+ return "float"
292
+
293
+ def infer_type(self, node: ast.AST) -> str:
294
+ if isinstance(node, ast.Constant):
295
+ if isinstance(node.value, bool):
296
+ return "bool"
297
+ if isinstance(node.value, int):
298
+ return "int"
299
+ if isinstance(node.value, float):
300
+ return "float"
301
+ if isinstance(node.value, str):
302
+ return "str"
303
+ if isinstance(node, ast.Name):
304
+ return self.var_types.get(node.id, "float")
305
+ if isinstance(node, ast.BinOp):
306
+ lt = self.infer_type(node.left)
307
+ rt = self.infer_type(node.right)
308
+ if isinstance(node.op, ast.Div):
309
+ return "float"
310
+ if "float" in (lt, rt):
311
+ return "float"
312
+ return "int"
313
+ if isinstance(node, ast.List):
314
+ return "list"
315
+ if isinstance(node, ast.Call):
316
+ fname = getattr(node.func, "id", None)
317
+ if fname == "len":
318
+ return "int"
319
+ if fname == "float":
320
+ return "float"
321
+ if fname == "int":
322
+ return "int"
323
+ return "float"
324
+
325
+ def binop_symbol(self, op: ast.AST) -> str:
326
+ return {
327
+ ast.Add: "+", ast.Sub: "-", ast.Mult: "*", ast.Mod: "%",
328
+ ast.BitAnd: "&", ast.BitOr: "|", ast.BitXor: "^",
329
+ }.get(type(op), "?")
330
+
331
+ def cmp_symbol(self, op: ast.AST) -> str:
332
+ return {
333
+ ast.Eq: "==", ast.NotEq: "!=", ast.Lt: "<", ast.LtE: "<=",
334
+ ast.Gt: ">", ast.GtE: ">=", ast.Is: "==", ast.IsNot: "!=",
335
+ ast.In: "in", ast.NotIn: "not in",
336
+ }.get(type(op), "?")
337
+
338
+
339
+ def emit_dart_functions(units: list[FuncUnit]) -> str:
340
+ """Emit all @dart functions as Dart source code."""
341
+ emitter = DartEmitter()
342
+ parts = [
343
+ "// AUTO-GENERATED @dart functions — transpiled from Python by GE.",
344
+ "",
345
+ ]
346
+ for u in units:
347
+ if u.forced_backend == "dart" and u.body is not None:
348
+ parts.append(emitter.emit_function(u))
349
+ return "\n".join(parts)