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,383 @@
1
+ """Configuration and compiler detection for pyeffic.
2
+
3
+ Toolchain detection is version-adaptive: it finds compilers on PATH or in
4
+ the local tools/ directory regardless of their version. Minimum supported
5
+ versions are checked and warnings are issued if a toolchain is too old,
6
+ but detection itself never fails due to a version mismatch.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import re
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+
18
+
19
+ # ---- Minimum supported versions per toolchain ----
20
+ # These are minimums — any version >= these will work.
21
+ # Updated toolchains should be backward-compatible per each language's semver policy.
22
+ MIN_VERSIONS: dict[str, str] = {
23
+ "rustc": "1.70.0", # Rust 1.70+ (stabilized many features we use)
24
+ "cpp": "10.0.0", # clang 10+ / GCC 10+ / MSVC 2019+
25
+ "dotnet": "8.0.0", # .NET 8+ for NativeAOT
26
+ "zig": "0.13.0", # Zig 0.13+ (API stabilized enough for our use)
27
+ "go": "1.21.0", # Go 1.21+ (toolchain management, go.mod required)
28
+ "kotlinc": "2.0.0", # Kotlin 2.0+ (stable Kotlin/Native @CName)
29
+ }
30
+
31
+
32
+ @dataclass
33
+ class ToolchainInfo:
34
+ """Information about a detected toolchain."""
35
+ path: str
36
+ version: str = "unknown"
37
+ is_supported: bool = True
38
+ warning: str = ""
39
+
40
+
41
+ @dataclass
42
+ class CompilerInfo:
43
+ rustc: str | None = None
44
+ cpp: str | None = None # one of g++/clang++/cl
45
+ cpp_kind: str | None = None # "gcc" | "clang" | "msvc"
46
+ dotnet: str | None = None # .NET SDK for C# NativeAOT
47
+ zig: str | None = None # Zig compiler
48
+ go: str | None = None # Go compiler
49
+ kotlinc: str | None = None # Kotlin compiler (kotlinc-native)
50
+ # version info for each toolchain
51
+ versions: dict[str, str] = field(default_factory=dict)
52
+ warnings: list[str] = field(default_factory=list)
53
+
54
+
55
+ @dataclass
56
+ class Config:
57
+ out_dir: Path = field(default_factory=lambda: Path("build"))
58
+ #: platform bucket: "desktop" | "web" | "mobile"
59
+ #: artifacts land in <out_dir>/<target>/ so a multi-target project stays
60
+ #: readable:
61
+ #: build/desktop/backend/ generated native sources
62
+ #: build/desktop/obj/ object files
63
+ #: build/desktop/bin/ executables
64
+ #: build/web/... web build
65
+ #: build/mobile/... Flutter project
66
+ target: str = "desktop"
67
+ keep_sources: bool = True
68
+ run_after_compile: bool = False
69
+ force_backend: str | None = None # "rust"|"cpp"|"csharp"|"zig"|"go"|"kotlin"|None (auto)
70
+ do_research: bool = True
71
+ research_timeout: float = 8.0
72
+ opt_level: str = "3" # -O3 / -C opt-level=3
73
+
74
+ @property
75
+ def target_dir(self) -> Path:
76
+ """Root of this target's artifacts: <out_dir>/<target>."""
77
+ return self.out_dir / self.target
78
+
79
+ def out(self, *parts: str) -> Path:
80
+ """A path under the target directory, with parents created."""
81
+ p = self.target_dir.joinpath(*parts)
82
+ p.parent.mkdir(parents=True, exist_ok=True)
83
+ return p
84
+
85
+ def bin_dir(self) -> Path:
86
+ """Where executables go: <out_dir>/<target>/bin."""
87
+ d = self.target_dir / "bin"
88
+ d.mkdir(parents=True, exist_ok=True)
89
+ return d
90
+
91
+ def obj_dir(self) -> Path:
92
+ """Where object files go: <out_dir>/<target>/obj."""
93
+ d = self.target_dir / "obj"
94
+ d.mkdir(parents=True, exist_ok=True)
95
+ return d
96
+
97
+ def backend_dir(self) -> Path:
98
+ """Where generated sources go: <out_dir>/<target>/backend."""
99
+ d = self.target_dir / "backend"
100
+ d.mkdir(parents=True, exist_ok=True)
101
+ return d
102
+
103
+
104
+ # ---- Tool finding ----
105
+
106
+ def _find_tool(name: str, subdir: str = "") -> str | None:
107
+ """Find a tool on PATH or in the local tools directory.
108
+
109
+ Searches in this order:
110
+ 1. System PATH (shutil.which)
111
+ 2. tools/ directory relative to the package (development mode)
112
+ 3. tools/ directory relative to the executable (standalone binary mode)
113
+ 4. tools/ directory relative to the current working directory
114
+
115
+ This is version-adaptive: it finds any version of the tool, not a
116
+ specific pinned version. Updated toolchains are detected automatically.
117
+ """
118
+ # 1) check PATH
119
+ found = shutil.which(name)
120
+ if found:
121
+ return found
122
+
123
+ # 2) check local tools directory relative to this file (development mode)
124
+ tools_dir = Path(__file__).resolve().parent.parent / "tools"
125
+ result = _search_tools_dir(tools_dir, name, subdir)
126
+ if result:
127
+ return result
128
+
129
+ # 3) check tools/ relative to the executable (standalone binary mode)
130
+ if hasattr(sys, "_MEIPASS") or getattr(sys, "frozen", False):
131
+ # PyInstaller: executable is in dist/ge/ge.exe, tools/ should be next to it
132
+ exe_dir = Path(sys.executable).resolve().parent
133
+ tools_dir = exe_dir / "tools"
134
+ result = _search_tools_dir(tools_dir, name, subdir)
135
+ if result:
136
+ return result
137
+ # also check parent of exe dir
138
+ tools_dir = exe_dir.parent / "tools"
139
+ result = _search_tools_dir(tools_dir, name, subdir)
140
+ if result:
141
+ return result
142
+
143
+ # 4) check tools/ relative to current working directory
144
+ tools_dir = Path.cwd() / "tools"
145
+ result = _search_tools_dir(tools_dir, name, subdir)
146
+ if result:
147
+ return result
148
+
149
+ return None
150
+
151
+
152
+ def _search_tools_dir(tools_dir: Path, name: str, subdir: str = "") -> str | None:
153
+ """Search a tools directory for a tool by name."""
154
+ if not tools_dir.exists():
155
+ return None
156
+
157
+ # direct path with subdir
158
+ if subdir:
159
+ p = tools_dir / subdir / f"{name}.exe"
160
+ if p.exists():
161
+ return str(p)
162
+ p = tools_dir / subdir / name
163
+ if p.exists():
164
+ return str(p)
165
+
166
+ # version-agnostic search: find any directory matching a prefix
167
+ name_lower = name.lower()
168
+ for child in sorted(tools_dir.iterdir(), reverse=True):
169
+ if not child.is_dir():
170
+ continue
171
+ child_lower = child.name.lower()
172
+ if child_lower.startswith(name_lower) or name_lower in child_lower:
173
+ p = child / f"{name}.exe"
174
+ if p.exists():
175
+ return str(p)
176
+ p = child / name
177
+ if p.exists():
178
+ return str(p)
179
+ p = child / "bin" / f"{name}.exe"
180
+ if p.exists():
181
+ return str(p)
182
+ p = child / "bin" / f"{name}.bat"
183
+ if p.exists():
184
+ return str(p)
185
+ p = child / "bin" / name
186
+ if p.exists():
187
+ return str(p)
188
+
189
+ # search one level deep
190
+ for child in tools_dir.iterdir():
191
+ if not child.is_dir():
192
+ continue
193
+ for sub in child.iterdir():
194
+ if sub.is_dir() and sub.name.lower() in ("bin", "lib", "cmd"):
195
+ p = sub / f"{name}.exe"
196
+ if p.exists():
197
+ return str(p)
198
+ p = sub / f"{name}.bat"
199
+ if p.exists():
200
+ return str(p)
201
+
202
+ return None
203
+
204
+
205
+ # ---- Version parsing ----
206
+
207
+ def _parse_version(output: str) -> str:
208
+ """Extract a version string from compiler --version output.
209
+
210
+ Handles formats like:
211
+ - "rustc 1.92.0 (ded5c06cf 2025-12-08)"
212
+ - "clang version 20.1.8"
213
+ - "go version go1.25.5 windows/amd64"
214
+ - "0.14.1" (Zig)
215
+ - "info: kotlinc-native 2.4.10 (JRE 25.0.1+8-LTS-27)"
216
+ - "10.0.101" (.NET)
217
+ """
218
+ # try common version patterns
219
+ patterns = [
220
+ r'(\d+\.\d+\.\d+(?:\.\d+)?)', # x.y.z or x.y.z.w
221
+ r'(\d+\.\d+)', # x.y (fallback)
222
+ ]
223
+ for pattern in patterns:
224
+ match = re.search(pattern, output)
225
+ if match:
226
+ return match.group(1)
227
+ return "unknown"
228
+
229
+
230
+ def _get_tool_version(tool_path: str, tool_name: str) -> str:
231
+ """Get the version of a toolchain by running it with the right version flag."""
232
+ try:
233
+ # different tools use different version flags
234
+ if tool_name == "zig":
235
+ cmd = [tool_path, "version"]
236
+ elif tool_name == "kotlinc":
237
+ cmd = [tool_path, "-version"]
238
+ elif tool_name == "dotnet":
239
+ cmd = [tool_path, "--version"]
240
+ elif tool_name == "go":
241
+ cmd = [tool_path, "version"] # Go uses "go version" not "go --version"
242
+ else:
243
+ cmd = [tool_path, "--version"]
244
+
245
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
246
+ output = (r.stdout or "") + (r.stderr or "")
247
+ return _parse_version(output)
248
+ except Exception:
249
+ return "unknown"
250
+
251
+
252
+ def _compare_versions(v1: str, v2: str) -> int:
253
+ """Compare two version strings. Returns -1, 0, or 1."""
254
+ parts1 = [int(x) for x in v1.split(".") if x.isdigit()]
255
+ parts2 = [int(x) for x in v2.split(".") if x.isdigit()]
256
+ # pad with zeros
257
+ while len(parts1) < len(parts2):
258
+ parts1.append(0)
259
+ while len(parts2) < len(parts1):
260
+ parts2.append(0)
261
+ for a, b in zip(parts1, parts2):
262
+ if a < b:
263
+ return -1
264
+ if a > b:
265
+ return 1
266
+ return 0
267
+
268
+
269
+ def _check_version(tool_name: str, found_version: str, min_version: str) -> str:
270
+ """Check if a tool version meets the minimum. Returns warning string or empty."""
271
+ if found_version == "unknown":
272
+ return f"Warning: could not determine {tool_name} version (expected >={min_version})"
273
+ if _compare_versions(found_version, min_version) < 0:
274
+ return f"Warning: {tool_name} {found_version} is older than recommended {min_version} — some features may not work"
275
+ return ""
276
+
277
+
278
+ # ---- Compiler detection ----
279
+
280
+ def detect_compilers() -> CompilerInfo:
281
+ """Detect all available compilers and their versions.
282
+
283
+ This function is version-adaptive: it finds any version of each toolchain
284
+ on PATH or in the local tools/ directory. It records versions and issues
285
+ warnings for outdated toolchains, but never fails to detect a tool.
286
+ """
287
+ info = CompilerInfo()
288
+
289
+ # Rust
290
+ info.rustc = _find_tool("rustc")
291
+ if info.rustc:
292
+ v = _get_tool_version(info.rustc, "rustc")
293
+ info.versions["rustc"] = v
294
+ w = _check_version("rustc", v, MIN_VERSIONS["rustc"])
295
+ if w:
296
+ info.warnings.append(w)
297
+
298
+ # C++ (try g++, clang++, cl in order)
299
+ for name, kind in (("g++", "gcc"), ("clang++", "clang"), ("cl", "msvc")):
300
+ found = _find_tool(name)
301
+ if found:
302
+ info.cpp = found
303
+ info.cpp_kind = kind
304
+ v = _get_tool_version(found, "cpp")
305
+ info.versions["cpp"] = v
306
+ w = _check_version("cpp", v, MIN_VERSIONS["cpp"])
307
+ if w:
308
+ info.warnings.append(w)
309
+ break
310
+
311
+ # C# (.NET)
312
+ info.dotnet = _find_tool("dotnet")
313
+ if info.dotnet:
314
+ v = _get_tool_version(info.dotnet, "dotnet")
315
+ info.versions["dotnet"] = v
316
+ w = _check_version("dotnet", v, MIN_VERSIONS["dotnet"])
317
+ if w:
318
+ info.warnings.append(w)
319
+
320
+ # Zig
321
+ info.zig = _find_tool("zig")
322
+ if info.zig:
323
+ v = _get_tool_version(info.zig, "zig")
324
+ info.versions["zig"] = v
325
+ w = _check_version("zig", v, MIN_VERSIONS["zig"])
326
+ if w:
327
+ info.warnings.append(w)
328
+
329
+ # Go
330
+ info.go = _find_tool("go")
331
+ if info.go:
332
+ v = _get_tool_version(info.go, "go")
333
+ info.versions["go"] = v
334
+ w = _check_version("go", v, MIN_VERSIONS["go"])
335
+ if w:
336
+ info.warnings.append(w)
337
+
338
+ # Kotlin (kotlinc-native for Kotlin/Native compilation)
339
+ info.kotlinc = _find_tool("kotlinc-native")
340
+ if info.kotlinc:
341
+ v = _get_tool_version(info.kotlinc, "kotlinc")
342
+ info.versions["kotlinc"] = v
343
+ w = _check_version("kotlinc", v, MIN_VERSIONS["kotlinc"])
344
+ if w:
345
+ info.warnings.append(w)
346
+
347
+ return info
348
+
349
+
350
+ def compiler_version(name: str) -> str:
351
+ """Get the version string of a compiler by name."""
352
+ try:
353
+ if name == "zig":
354
+ cmd = [name, "version"]
355
+ elif name == "kotlinc-native":
356
+ cmd = [name, "-version"]
357
+ else:
358
+ cmd = [name, "--version"]
359
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
360
+ return (r.stdout or r.stderr).strip().splitlines()[0] if (r.stdout or r.stderr) else "unknown"
361
+ except Exception:
362
+ return "unknown"
363
+
364
+
365
+ def print_toolchain_report(info: CompilerInfo) -> None:
366
+ """Print a human-readable report of detected toolchains."""
367
+ print("=== GE Toolchain Report ===")
368
+ tools = [
369
+ ("Rust", info.rustc, info.versions.get("rustc", "—")),
370
+ ("C++", info.cpp, info.versions.get("cpp", "—")),
371
+ ("C# (.NET)", info.dotnet, info.versions.get("dotnet", "—")),
372
+ ("Zig", info.zig, info.versions.get("zig", "—")),
373
+ ("Go", info.go, info.versions.get("go", "—")),
374
+ ("Kotlin", info.kotlinc, info.versions.get("kotlinc", "—")),
375
+ ]
376
+ for name, path, version in tools:
377
+ status = "[OK]" if path else "[--]"
378
+ ver_str = f"v{version}" if version != "-" else ""
379
+ print(f" {status} {name:12} {ver_str:12} {path or 'not found'}")
380
+ if info.warnings:
381
+ print("\nWarnings:")
382
+ for w in info.warnings:
383
+ print(f" [!] {w}")