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.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +535 -0
- package/bin/ge.js +112 -0
- package/package.json +62 -0
- package/python/pyeffic/__init__.py +9 -0
- package/python/pyeffic/__main__.py +6 -0
- package/python/pyeffic/analyzer.py +464 -0
- package/python/pyeffic/apisurface.py +238 -0
- package/python/pyeffic/autoselect.py +327 -0
- package/python/pyeffic/backends.py +87 -0
- package/python/pyeffic/bench.py +233 -0
- package/python/pyeffic/cli.py +184 -0
- package/python/pyeffic/compiler.py +421 -0
- package/python/pyeffic/config.py +383 -0
- package/python/pyeffic/dartgen.py +441 -0
- package/python/pyeffic/deploy.py +586 -0
- package/python/pyeffic/diagnostics.py +194 -0
- package/python/pyeffic/difftest.py +424 -0
- package/python/pyeffic/downloader.py +307 -0
- package/python/pyeffic/emitters/__init__.py +11 -0
- package/python/pyeffic/emitters/base.py +2359 -0
- package/python/pyeffic/emitters/cpp.py +266 -0
- package/python/pyeffic/emitters/csharp.py +342 -0
- package/python/pyeffic/emitters/dart.py +349 -0
- package/python/pyeffic/emitters/go.py +388 -0
- package/python/pyeffic/emitters/kotlin.py +314 -0
- package/python/pyeffic/emitters/rust.py +314 -0
- package/python/pyeffic/emitters/zig.py +411 -0
- package/python/pyeffic/ffi.py +49 -0
- package/python/pyeffic/frontends/__init__.py +94 -0
- package/python/pyeffic/frontends/hybrid.py +709 -0
- package/python/pyeffic/frontends/typescript.py +965 -0
- package/python/pyeffic/ge_cli.py +1148 -0
- package/python/pyeffic/golden.py +348 -0
- package/python/pyeffic/idents.py +206 -0
- package/python/pyeffic/modules.py +220 -0
- package/python/pyeffic/packer.py +222 -0
- package/python/pyeffic/pipeline.py +797 -0
- package/python/pyeffic/reactgen.py +966 -0
- package/python/pyeffic/researcher.py +177 -0
- package/python/pyeffic/scaffold.py +397 -0
- package/python/pyeffic/stdlib.py +246 -0
- package/python/pyeffic/styling.py +220 -0
- package/python/pyeffic/templates/desktop_gui/README.md +106 -0
- package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
- package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
- package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
- package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
- package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
- package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
- package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
- package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
- package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
- package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
- package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
- package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
- package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
- package/python/pyeffic/templates/web_react/README.md +115 -0
- package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
- package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
- package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
- package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
- package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
- package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
- package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
- package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
- package/python/pyeffic/templates/web_react/ge.toml +23 -0
- package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
- package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
- package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
- package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
- package/python/pyeffic/ts2py.py +657 -0
- package/python/pyeffic/typecheck.py +232 -0
- package/python/pyeffic/ui.py +154 -0
- package/python/pyeffic/ui_dsl.py +618 -0
- package/python/pyeffic/widgets.py +87 -0
- package/scripts/README.md +42 -0
- package/scripts/check-toolchains.py +85 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""API surface dump and check.
|
|
2
|
+
|
|
3
|
+
A committed description of the surfaces users depend on, so a change to them
|
|
4
|
+
is a deliberate, reviewable act rather than an accident.
|
|
5
|
+
|
|
6
|
+
api/cli.api every command, flag and argument
|
|
7
|
+
api/diagnostics.api every GE*** code and its meaning
|
|
8
|
+
api/intrinsics.api the compiler intrinsics and their backends
|
|
9
|
+
|
|
10
|
+
Modelled on Kotlin's `apiDump`/`apiCheck` and Go's `apidiff`: the dump is
|
|
11
|
+
committed, `--check` fails when it drifts, and changing it means running
|
|
12
|
+
`--update`, reviewing the diff, and committing it.
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
-----
|
|
16
|
+
ge api-check fail on unexplained surface change
|
|
17
|
+
ge api-dump regenerate the dumps (review, then commit)
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
#: backend decorators and the intrinsics every frontend exposes
|
|
25
|
+
INTRINSICS = {
|
|
26
|
+
"ge_inline": "target-specific inline code, function body",
|
|
27
|
+
"ge_raw": "unconditional raw code, function body",
|
|
28
|
+
"ge_preamble": "file-scope code injection, module level",
|
|
29
|
+
"gePreamble": "TypeScript-flavour spelling of ge_preamble",
|
|
30
|
+
"geInline": "TypeScript-flavour spelling of ge_inline",
|
|
31
|
+
"geRaw": "TypeScript-flavour spelling of ge_raw",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
BACKEND_DECORATORS = ("rust", "cpp", "csharp", "zig", "go", "kotlin", "dart")
|
|
35
|
+
|
|
36
|
+
#: source extensions and the frontend each selects
|
|
37
|
+
FRONTENDS = {
|
|
38
|
+
".ge": "hybrid",
|
|
39
|
+
".ge.py": "python",
|
|
40
|
+
".ge.ts": "typescript",
|
|
41
|
+
".ts.ge.py": "typescript (legacy alias)",
|
|
42
|
+
".ge.ui": "ui DSL",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#: where the dumps live, relative to the repo root
|
|
46
|
+
API_DIR = "api"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def api_root() -> Path:
|
|
50
|
+
return Path(__file__).resolve().parent.parent / API_DIR
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Dump generation
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
def dump_cli() -> str:
|
|
58
|
+
"""Every command, flag and argument, sorted and stable."""
|
|
59
|
+
import argparse
|
|
60
|
+
|
|
61
|
+
from . import ge_cli
|
|
62
|
+
|
|
63
|
+
# build the parser the same way the CLI does
|
|
64
|
+
parser = argparse.ArgumentParser(prog="ge", add_help=False)
|
|
65
|
+
sub = parser.add_subparsers(dest="cmd")
|
|
66
|
+
|
|
67
|
+
# ge_cli.main() constructs the parser inline; capture it by running the
|
|
68
|
+
# module's parser builder if exposed, otherwise introspect the actions
|
|
69
|
+
# of a freshly built parser via a dry run.
|
|
70
|
+
import io
|
|
71
|
+
from contextlib import redirect_stderr, redirect_stdout
|
|
72
|
+
|
|
73
|
+
captured = None
|
|
74
|
+
|
|
75
|
+
real_add_subparsers = argparse.ArgumentParser.add_subparsers
|
|
76
|
+
|
|
77
|
+
def spy(self, *a, **kw):
|
|
78
|
+
nonlocal captured
|
|
79
|
+
sp = real_add_subparsers(self, *a, **kw)
|
|
80
|
+
captured = self
|
|
81
|
+
return sp
|
|
82
|
+
|
|
83
|
+
argparse.ArgumentParser.add_subparsers = spy
|
|
84
|
+
try:
|
|
85
|
+
with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()):
|
|
86
|
+
try:
|
|
87
|
+
ge_cli.main(["compilers"])
|
|
88
|
+
except SystemExit:
|
|
89
|
+
pass
|
|
90
|
+
finally:
|
|
91
|
+
argparse.ArgumentParser.add_subparsers = real_add_subparsers
|
|
92
|
+
|
|
93
|
+
lines: list[str] = []
|
|
94
|
+
if captured is not None:
|
|
95
|
+
for action in captured._actions:
|
|
96
|
+
if isinstance(action, argparse._SubParsersAction):
|
|
97
|
+
for name in sorted(action.choices):
|
|
98
|
+
sub_parser = action.choices[name]
|
|
99
|
+
# one line per (command, surface) pair so a diff names the
|
|
100
|
+
# exact command that changed
|
|
101
|
+
lines.append(f"{name} <command>")
|
|
102
|
+
for sub_action in sub_parser._actions:
|
|
103
|
+
if sub_action.option_strings:
|
|
104
|
+
opts = ", ".join(sorted(sub_action.option_strings))
|
|
105
|
+
lines.append(f"{name} option {opts}")
|
|
106
|
+
elif sub_action.dest not in ("help",):
|
|
107
|
+
suffix = "" if sub_action.nargs != "?" else "?"
|
|
108
|
+
lines.append(f"{name} arg {sub_action.dest}{suffix}")
|
|
109
|
+
lines.sort()
|
|
110
|
+
return "\n".join(lines) + "\n"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def dump_diagnostics() -> str:
|
|
114
|
+
"""Every diagnostic code the compiler can emit."""
|
|
115
|
+
from .diagnostics import ErrorReporter # noqa: F401
|
|
116
|
+
|
|
117
|
+
known = {
|
|
118
|
+
"GE001": "unsupported construct / import warning",
|
|
119
|
+
"GE002": "type mismatch or lossy conversion",
|
|
120
|
+
"GE003": "reserved for future use",
|
|
121
|
+
"GE004": "reserved for future use",
|
|
122
|
+
"GE005": "native compilation failed",
|
|
123
|
+
"GE006": "reserved for future use",
|
|
124
|
+
"GE007": "function called but not defined",
|
|
125
|
+
"GE008": "entry point not found",
|
|
126
|
+
"GE009": "reserved for future use",
|
|
127
|
+
"GE010": "source frontend error (TypeScript/hybrid lowering)",
|
|
128
|
+
"GE011": "identifier renamed to avoid a target-language keyword",
|
|
129
|
+
"GE020": "deprecated form (planned)",
|
|
130
|
+
}
|
|
131
|
+
lines = [f"{code} {text}" for code, text in sorted(known.items())]
|
|
132
|
+
return "\n".join(lines) + "\n"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def dump_intrinsics() -> str:
|
|
136
|
+
lines: list[str] = []
|
|
137
|
+
for name, desc in sorted(INTRINSICS.items()):
|
|
138
|
+
lines.append(f"intrinsic {name}: {desc}")
|
|
139
|
+
for dec in sorted(BACKEND_DECORATORS):
|
|
140
|
+
lines.append(f"decorator @{dec}")
|
|
141
|
+
for ext, frontend in sorted(FRONTENDS.items()):
|
|
142
|
+
lines.append(f"extension {ext} -> {frontend}")
|
|
143
|
+
return "\n".join(lines) + "\n"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def all_dumps() -> dict[str, str]:
|
|
147
|
+
return {
|
|
148
|
+
"cli.api": dump_cli(),
|
|
149
|
+
"diagnostics.api": dump_diagnostics(),
|
|
150
|
+
"intrinsics.api": dump_intrinsics(),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# Check / update
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
def check(root: Path | None = None) -> tuple[list[str], list[str]]:
|
|
159
|
+
"""Return (changed, missing) file names."""
|
|
160
|
+
root = root or api_root()
|
|
161
|
+
changed: list[str] = []
|
|
162
|
+
missing: list[str] = []
|
|
163
|
+
for name, content in all_dumps().items():
|
|
164
|
+
p = root / name
|
|
165
|
+
if not p.exists():
|
|
166
|
+
missing.append(name)
|
|
167
|
+
continue
|
|
168
|
+
if p.read_text(encoding="utf-8").replace("\r\n", "\n") != content:
|
|
169
|
+
changed.append(name)
|
|
170
|
+
return changed, missing
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def update(root: Path | None = None) -> list[str]:
|
|
174
|
+
root = root or api_root()
|
|
175
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
written: list[str] = []
|
|
177
|
+
for name, content in all_dumps().items():
|
|
178
|
+
p = root / name
|
|
179
|
+
old = p.read_text(encoding="utf-8") if p.exists() else None
|
|
180
|
+
if old is None or old.replace("\r\n", "\n") != content:
|
|
181
|
+
p.write_text(content, encoding="utf-8")
|
|
182
|
+
written.append(name)
|
|
183
|
+
return written
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# CLI
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def main(argv: list[str] | None = None) -> int:
|
|
191
|
+
import argparse
|
|
192
|
+
|
|
193
|
+
p = argparse.ArgumentParser(
|
|
194
|
+
prog="ge api-check",
|
|
195
|
+
description="Check or regenerate the committed API surface dumps")
|
|
196
|
+
p.add_argument("--update", action="store_true",
|
|
197
|
+
help="regenerate the dumps (review the diff, then commit)")
|
|
198
|
+
args = p.parse_args(argv)
|
|
199
|
+
|
|
200
|
+
root = api_root()
|
|
201
|
+
|
|
202
|
+
if args.update:
|
|
203
|
+
written = update(root)
|
|
204
|
+
print("GE API surface — update")
|
|
205
|
+
print(f" dir : {root}")
|
|
206
|
+
print(f" written: {len(written)}")
|
|
207
|
+
for name in written:
|
|
208
|
+
print(f" {name}")
|
|
209
|
+
if not written:
|
|
210
|
+
print(" (no changes)")
|
|
211
|
+
print()
|
|
212
|
+
print("review the diff, then commit it")
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
changed, missing = check(root)
|
|
216
|
+
print("GE API surface — check")
|
|
217
|
+
print(f" dir: {root}")
|
|
218
|
+
print()
|
|
219
|
+
if not changed and not missing:
|
|
220
|
+
print("the committed API surface matches the compiler")
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
if missing:
|
|
224
|
+
print("missing dumps (run: ge api-check --update):")
|
|
225
|
+
for name in missing:
|
|
226
|
+
print(f" {name}")
|
|
227
|
+
if changed:
|
|
228
|
+
print("changed surface (review, then: ge api-check --update):")
|
|
229
|
+
for name in changed:
|
|
230
|
+
print(f" {name}")
|
|
231
|
+
print()
|
|
232
|
+
print("a change here is a change users can see — treat it as breaking")
|
|
233
|
+
print("unless it is purely additive")
|
|
234
|
+
return 1
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if __name__ == "__main__":
|
|
238
|
+
sys.exit(main())
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Auto-selection rule engine for GE backend selection.
|
|
2
|
+
|
|
3
|
+
When the user does NOT specify a backend with @rust/@cpp/@csharp/@zig/@go/@kotlin,
|
|
4
|
+
the auto-selector analyzes each function's characteristics and picks the best
|
|
5
|
+
backend based on research-backed rules.
|
|
6
|
+
|
|
7
|
+
Rules are derived from web research on best use cases for each language:
|
|
8
|
+
|
|
9
|
+
Rust — memory safety, concurrency, systems, security-sensitive, long-running services
|
|
10
|
+
C++ — game engines, CUDA, OpenCV, performance-critical computation, template metaprogramming
|
|
11
|
+
C# — enterprise logic, game dev (Unity), .NET ecosystem, business logic, data processing
|
|
12
|
+
Dart — Flutter UI, async event loop, state management, cross-platform UI
|
|
13
|
+
Zig — embedded systems, toolchains, C replacement, manual memory control, no runtime
|
|
14
|
+
Go — network services, microservices, cloud infrastructure, goroutine concurrency
|
|
15
|
+
Kotlin — Android, Kotlin Multiplatform, JVM backend, coroutines, null-safety
|
|
16
|
+
|
|
17
|
+
The engine scores each backend for each function based on detected features,
|
|
18
|
+
then picks the highest-scoring backend. If all scores are zero, it defaults to
|
|
19
|
+
Rust (the safest general-purpose systems language).
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import ast
|
|
24
|
+
import re
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from .analyzer import FuncUnit
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class BackendScore:
|
|
31
|
+
"""Score for a single backend for a single function."""
|
|
32
|
+
backend: str
|
|
33
|
+
score: float = 0.0
|
|
34
|
+
reasons: list[str] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
def add(self, points: float, reason: str) -> None:
|
|
37
|
+
self.score += points
|
|
38
|
+
self.reasons.append(f"+{points:.1f} {self.backend} ({reason})")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---- Feature detection rules ----
|
|
42
|
+
# Each rule: (pattern_name, feature_keywords, backend_scores)
|
|
43
|
+
# The analyzer's feature detection already classifies functions; we map those
|
|
44
|
+
# features to backend scores based on research.
|
|
45
|
+
|
|
46
|
+
# Research-backed scoring rules:
|
|
47
|
+
# - Higher score = better fit for that backend
|
|
48
|
+
# - Negative score = poor fit (avoid)
|
|
49
|
+
# - Zero = neutral
|
|
50
|
+
|
|
51
|
+
RULES: list[tuple[str, dict[str, float]]] = [
|
|
52
|
+
# ---- Memory safety / security ----
|
|
53
|
+
# Rust is the premier choice for memory safety without GC
|
|
54
|
+
("memory_safety_critical", {
|
|
55
|
+
"rust": +3.0, # Rust: ownership model eliminates memory bugs
|
|
56
|
+
"zig": +1.5, # Zig: safer than C but manual memory
|
|
57
|
+
"csharp": +0.5, # C#: GC-managed, safe but not systems-level
|
|
58
|
+
"kotlin": +0.5, # Kotlin: GC-managed, null-safe
|
|
59
|
+
"cpp": -1.0, # C++: many unsafe-by-default edges
|
|
60
|
+
"go": +0.5, # Go: GC-managed, safe for ordinary code
|
|
61
|
+
}),
|
|
62
|
+
|
|
63
|
+
# ---- Concurrency ----
|
|
64
|
+
# Go excels at goroutine concurrency; Rust has type-system-enforced concurrency
|
|
65
|
+
("concurrency", {
|
|
66
|
+
"go": +3.0, # Go: goroutines are the killer feature
|
|
67
|
+
"rust": +2.5, # Rust: Send/Sync type-system-enforced concurrency
|
|
68
|
+
"kotlin": +2.0, # Kotlin: coroutines for lightweight async
|
|
69
|
+
"csharp": +1.5, # C#: async/await, Task Parallel Library
|
|
70
|
+
"cpp": +1.0, # C++: threads, atomics, but complex
|
|
71
|
+
"zig": +0.5, # Zig: evolving async model
|
|
72
|
+
"dart": +1.0, # Dart: async/await, isolates
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
# ---- Numeric computation / arithmetic ----
|
|
76
|
+
# C++ and Rust are top performers; C# for enterprise data processing
|
|
77
|
+
("arithmetic", {
|
|
78
|
+
"cpp": +2.0, # C++: excellent performance, SIMD, auto-vectorization
|
|
79
|
+
"rust": +2.0, # Rust: competitive with C++, SIMD auto-vectorization
|
|
80
|
+
"csharp": +1.5, # C#: good performance, .NET numerics
|
|
81
|
+
"zig": +1.5, # Zig: excellent potential, explicit allocation
|
|
82
|
+
"go": +1.0, # Go: good but GC affects some workloads
|
|
83
|
+
"kotlin": +0.5, # Kotlin: JVM overhead for pure computation
|
|
84
|
+
"dart": +0.5, # Dart: not optimized for heavy computation
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
# ---- Loops / iteration ----
|
|
88
|
+
# C++ and Rust excel at tight loops; Go for simple iteration
|
|
89
|
+
("numeric_loop", {
|
|
90
|
+
"cpp": +2.0, # C++: tight loop optimization, auto-vectorization
|
|
91
|
+
"rust": +2.0, # Rust: zero-cost iterators, auto-vectorization
|
|
92
|
+
"zig": +1.5, # Zig: explicit control, fast compilation
|
|
93
|
+
"csharp": +1.0, # C#: good loop performance
|
|
94
|
+
"go": +1.0, # Go: simple loops, fast enough
|
|
95
|
+
"kotlin": +0.5, # Kotlin: functional iteration overhead
|
|
96
|
+
"dart": +0.3, # Dart: not for heavy loops
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
# ---- Comparisons / conditionals ----
|
|
100
|
+
# All languages handle this well; slight edge to systems languages
|
|
101
|
+
("comparison", {
|
|
102
|
+
"rust": +1.0, # Rust: pattern matching, exhaustive
|
|
103
|
+
"cpp": +1.0, # C++: standard conditionals
|
|
104
|
+
"zig": +1.0, # Zig: no hidden control flow
|
|
105
|
+
"csharp": +0.5, # C#: standard
|
|
106
|
+
"go": +0.5, # Go: simple conditionals
|
|
107
|
+
"kotlin": +0.5, # Kotlin: when expressions
|
|
108
|
+
"dart": +0.3, # Dart: standard
|
|
109
|
+
}),
|
|
110
|
+
|
|
111
|
+
# ---- I/O (print, input) ----
|
|
112
|
+
# All handle I/O; Dart for UI output, Go for network I/O
|
|
113
|
+
("io_print", {
|
|
114
|
+
"dart": +2.0, # Dart: UI output, console for debugging
|
|
115
|
+
"go": +1.5, # Go: excellent I/O stdlib
|
|
116
|
+
"kotlin": +1.0, # Kotlin: JVM I/O
|
|
117
|
+
"csharp": +1.0, # C#: Console I/O
|
|
118
|
+
"rust": +0.5, # Rust: basic I/O
|
|
119
|
+
"cpp": +0.5, # C++: iostream
|
|
120
|
+
"zig": +0.5, # Zig: basic I/O
|
|
121
|
+
}),
|
|
122
|
+
|
|
123
|
+
# ---- List / array operations ----
|
|
124
|
+
# C++ for performance; Rust for safety; Go for simplicity
|
|
125
|
+
("list_operation", {
|
|
126
|
+
"cpp": +2.0, # C++: std::vector, performance
|
|
127
|
+
"rust": +2.0, # Rust: Vec, safe and fast
|
|
128
|
+
"zig": +1.5, # Zig: slices, explicit allocators
|
|
129
|
+
"csharp": +1.5, # C#: LINQ, List<T>
|
|
130
|
+
"go": +1.0, # Go: slices, simple
|
|
131
|
+
"kotlin": +1.0, # Kotlin: collections, functional
|
|
132
|
+
"dart": +0.5, # Dart: List, not performance-focused
|
|
133
|
+
}),
|
|
134
|
+
|
|
135
|
+
# ---- Function calls (inter-function) ----
|
|
136
|
+
# All handle this; slight edge to languages with good inlining
|
|
137
|
+
("function_call", {
|
|
138
|
+
"rust": +1.0, # Rust: zero-cost abstractions, inlining
|
|
139
|
+
"cpp": +1.0, # C++: inlining, templates
|
|
140
|
+
"zig": +1.0, # Zig: comptime, inlining
|
|
141
|
+
"csharp": +0.5, # C#: JIT inlining (AOT less aggressive)
|
|
142
|
+
"go": +0.5, # Go: inlining but interface overhead
|
|
143
|
+
"kotlin": +0.5, # Kotlin: JVM inlining
|
|
144
|
+
"dart": +0.3, # Dart: standard
|
|
145
|
+
}),
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---- Name-based heuristics ----
|
|
150
|
+
# Function name patterns that suggest a specific backend
|
|
151
|
+
NAME_PATTERNS: list[tuple[re.Pattern, dict[str, float]]] = [
|
|
152
|
+
# UI / widget / screen / page → Dart
|
|
153
|
+
(re.compile(r"^(build|render|widget|screen|page|view|drawer|dialog|snackbar|appbar)", re.I), {
|
|
154
|
+
"dart": +5.0, # Flutter UI is Dart's domain
|
|
155
|
+
"kotlin": +1.0, # Compose also possible
|
|
156
|
+
}),
|
|
157
|
+
# Network / HTTP / API / request → Go
|
|
158
|
+
(re.compile(r"^(fetch|request|http|api|endpoint|route|handler|serve|listen|connect|socket|websocket)", re.I), {
|
|
159
|
+
"go": +5.0, # Go: network services, goroutines
|
|
160
|
+
"kotlin": +1.5, # Kotlin: Ktor, coroutines
|
|
161
|
+
"csharp": +1.0, # C#: ASP.NET
|
|
162
|
+
}),
|
|
163
|
+
# Compute / calculate / process → C++ or Rust
|
|
164
|
+
(re.compile(r"^(compute|calculate|process|transform|optimize|render|encode|decode|compress|encrypt|decrypt|hash)", re.I), {
|
|
165
|
+
"cpp": +3.0, # C++: performance-critical computation
|
|
166
|
+
"rust": +2.5, # Rust: safe computation
|
|
167
|
+
"zig": +2.0, # Zig: explicit control
|
|
168
|
+
"csharp": +1.0, # C#: data processing
|
|
169
|
+
}),
|
|
170
|
+
# Memory / allocate / free / buffer → Zig or Rust
|
|
171
|
+
(re.compile(r"^(alloc|free|buffer|memory|pointer|register|hardware|device|firmware|embedded)", re.I), {
|
|
172
|
+
"zig": +5.0, # Zig: manual memory control, embedded
|
|
173
|
+
"rust": +2.0, # Rust: ownership model
|
|
174
|
+
"cpp": +1.0, # C++: manual memory
|
|
175
|
+
}),
|
|
176
|
+
# Business / enterprise / model / service / validate → C# or Kotlin
|
|
177
|
+
(re.compile(r"^(business|enterprise|model|service|validate|validate|repository|entity|domain|logic|rule|policy)", re.I), {
|
|
178
|
+
"csharp": +4.0, # C#: enterprise logic, .NET
|
|
179
|
+
"kotlin": +3.0, # Kotlin: JVM backend, domain logic
|
|
180
|
+
"go": +1.0, # Go: services
|
|
181
|
+
}),
|
|
182
|
+
# Android / mobile / jetpack / compose → Kotlin
|
|
183
|
+
(re.compile(r"^(android|mobile|jetpack|compose|lifecycle|viewmodel|navigation)", re.I), {
|
|
184
|
+
"kotlin": +5.0, # Kotlin: Android-native
|
|
185
|
+
"dart": +2.0, # Dart: Flutter mobile
|
|
186
|
+
}),
|
|
187
|
+
# Concurrent / async / parallel / goroutine → Go
|
|
188
|
+
(re.compile(r"^(concurrent|async|parallel|goroutine|channel|pipeline|worker|spawn|schedule)", re.I), {
|
|
189
|
+
"go": +5.0, # Go: goroutines, channels
|
|
190
|
+
"rust": +2.0, # Rust: async, threads
|
|
191
|
+
"kotlin": +2.0, # Kotlin: coroutines
|
|
192
|
+
"csharp": +1.5, # C#: async/await
|
|
193
|
+
}),
|
|
194
|
+
# Safe / secure / verify / check / guard → Rust
|
|
195
|
+
(re.compile(r"^(safe|secure|verify|check|guard|protect|sanitize|validate_token|auth|permission)", re.I), {
|
|
196
|
+
"rust": +5.0, # Rust: memory safety, security
|
|
197
|
+
"kotlin": +1.0, # Kotlin: null-safety
|
|
198
|
+
"csharp": +1.0, # C#: safe by default
|
|
199
|
+
}),
|
|
200
|
+
# Game / render / physics / collision / sprite → C++
|
|
201
|
+
(re.compile(r"^(game|render|physics|collision|sprite|mesh|shader|texture|vertex|polygon|raycast)", re.I), {
|
|
202
|
+
"cpp": +5.0, # C++: game engines, rendering
|
|
203
|
+
"csharp": +2.0, # C#: Unity game dev
|
|
204
|
+
"rust": +1.0, # Rust: emerging game dev
|
|
205
|
+
}),
|
|
206
|
+
# Data / dataframe / query / filter / map / reduce → C# or Kotlin
|
|
207
|
+
(re.compile(r"^(data|dataframe|query|filter|map|reduce|aggregate|group|sort|search|index)", re.I), {
|
|
208
|
+
"csharp": +3.0, # C#: LINQ, data processing
|
|
209
|
+
"kotlin": +2.5, # Kotlin: collections, functional
|
|
210
|
+
"go": +1.0, # Go: simple data handling
|
|
211
|
+
"rust": +1.0, # Rust: iterators
|
|
212
|
+
}),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ---- Default backend preference order (when scores are tied) ----
|
|
217
|
+
# Based on research: Rust is the safest default for general-purpose code
|
|
218
|
+
DEFAULT_PREFERENCE = ["rust", "cpp", "csharp", "go", "kotlin", "zig", "dart"]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def score_backend(unit: FuncUnit) -> dict[str, BackendScore]:
|
|
222
|
+
"""Score all backends for a given function.
|
|
223
|
+
|
|
224
|
+
Returns a dict mapping backend name to BackendScore.
|
|
225
|
+
"""
|
|
226
|
+
scores = {b: BackendScore(backend=b) for b in
|
|
227
|
+
["rust", "cpp", "csharp", "zig", "go", "kotlin", "dart"]}
|
|
228
|
+
|
|
229
|
+
# 1) Apply feature-based rules
|
|
230
|
+
for feature in unit.features:
|
|
231
|
+
for pattern_name, backend_scores in RULES:
|
|
232
|
+
if pattern_name == feature or feature.startswith(pattern_name):
|
|
233
|
+
for backend, points in backend_scores.items():
|
|
234
|
+
if points > 0:
|
|
235
|
+
scores[backend].add(points, feature)
|
|
236
|
+
elif points < 0:
|
|
237
|
+
scores[backend].score += points
|
|
238
|
+
scores[backend].reasons.append(f"{points:.1f} {backend} ({feature})")
|
|
239
|
+
|
|
240
|
+
# 2) Apply name-based heuristics
|
|
241
|
+
for pattern, backend_scores in NAME_PATTERNS:
|
|
242
|
+
if pattern.match(unit.name):
|
|
243
|
+
for backend, points in backend_scores.items():
|
|
244
|
+
if points > 0:
|
|
245
|
+
scores[backend].add(points, f"name pattern: {unit.name}")
|
|
246
|
+
|
|
247
|
+
# 3) Check for UI-related patterns in the function body (build function)
|
|
248
|
+
if unit.name == "build" or unit.name.startswith("build_"):
|
|
249
|
+
scores["dart"].add(5.0, "UI build function")
|
|
250
|
+
|
|
251
|
+
# 4) Check for async/event-loop patterns
|
|
252
|
+
if unit.body:
|
|
253
|
+
for node in ast.walk(unit.body):
|
|
254
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
|
|
255
|
+
fname = node.func.id
|
|
256
|
+
# network-like calls suggest Go
|
|
257
|
+
if fname in ("fetch", "request", "send", "receive", "listen", "connect"):
|
|
258
|
+
scores["go"].add(2.0, f"network call: {fname}")
|
|
259
|
+
# async-like patterns suggest Go or Kotlin
|
|
260
|
+
if fname in ("async", "await", "spawn", "go"):
|
|
261
|
+
scores["go"].add(1.5, f"async call: {fname}")
|
|
262
|
+
scores["kotlin"].add(1.0, f"async call: {fname}")
|
|
263
|
+
|
|
264
|
+
return scores
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def select_backend(unit: FuncUnit) -> tuple[str, list[str]]:
|
|
268
|
+
"""Select the best backend for a function based on auto-selection rules.
|
|
269
|
+
|
|
270
|
+
Returns (backend_name, list_of_reasons).
|
|
271
|
+
"""
|
|
272
|
+
scores = score_backend(unit)
|
|
273
|
+
|
|
274
|
+
# find the highest-scoring backend
|
|
275
|
+
best_backend = None
|
|
276
|
+
best_score = 0.0
|
|
277
|
+
for backend in DEFAULT_PREFERENCE:
|
|
278
|
+
s = scores[backend].score
|
|
279
|
+
if s > best_score:
|
|
280
|
+
best_score = s
|
|
281
|
+
best_backend = backend
|
|
282
|
+
|
|
283
|
+
# if all scores are zero, default to Rust
|
|
284
|
+
if best_backend is None or best_score <= 0:
|
|
285
|
+
best_backend = "rust"
|
|
286
|
+
return best_backend, ["default: no strong signal -> rust (safest general-purpose)"]
|
|
287
|
+
|
|
288
|
+
# collect reasons
|
|
289
|
+
reasons = scores[best_backend].reasons.copy()
|
|
290
|
+
# add comparison summary
|
|
291
|
+
summary_parts = []
|
|
292
|
+
for backend in DEFAULT_PREFERENCE:
|
|
293
|
+
s = scores[backend].score
|
|
294
|
+
if s != 0:
|
|
295
|
+
summary_parts.append(f"{backend}={s:.1f}")
|
|
296
|
+
if summary_parts:
|
|
297
|
+
reasons.append(f"scores: {', '.join(summary_parts)}")
|
|
298
|
+
|
|
299
|
+
return best_backend, reasons
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def auto_select_backends(units: list[FuncUnit]) -> dict[str, tuple[str, list[str]]]:
|
|
303
|
+
"""Auto-select backends for all functions.
|
|
304
|
+
|
|
305
|
+
Returns a dict mapping function name to (backend, reasons).
|
|
306
|
+
Only selects for functions that don't have a forced_backend.
|
|
307
|
+
"""
|
|
308
|
+
result: dict[str, tuple[str, list[str]]] = {}
|
|
309
|
+
for u in units:
|
|
310
|
+
if u.forced_backend:
|
|
311
|
+
result[u.name] = (u.forced_backend, [f"forced: @{u.forced_backend}"])
|
|
312
|
+
elif u.supported and u.name != "build":
|
|
313
|
+
backend, reasons = select_backend(u)
|
|
314
|
+
result[u.name] = (backend, reasons)
|
|
315
|
+
return result
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def explain_selection(unit: FuncUnit) -> str:
|
|
319
|
+
"""Explain why a particular backend was selected for a function.
|
|
320
|
+
|
|
321
|
+
Returns a human-readable explanation string.
|
|
322
|
+
"""
|
|
323
|
+
backend, reasons = select_backend(unit)
|
|
324
|
+
lines = [f"Function '{unit.name}' -> {backend}"]
|
|
325
|
+
for r in reasons:
|
|
326
|
+
lines.append(f" {r}")
|
|
327
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Backend selection decorators for GE.
|
|
2
|
+
|
|
3
|
+
Usage in GE source:
|
|
4
|
+
from pyeffic.backends import rust, cpp, dart, csharp, zig, go, kotlin
|
|
5
|
+
|
|
6
|
+
@rust
|
|
7
|
+
def safe_function(x: int) -> int:
|
|
8
|
+
return x * 2
|
|
9
|
+
|
|
10
|
+
@cpp
|
|
11
|
+
def cpp_function(x: int) -> int:
|
|
12
|
+
return x * 3
|
|
13
|
+
|
|
14
|
+
@dart
|
|
15
|
+
def ui_function():
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@csharp
|
|
19
|
+
def enterprise_logic(x: int) -> int:
|
|
20
|
+
return x * 4
|
|
21
|
+
|
|
22
|
+
@zig
|
|
23
|
+
def systems_function(x: int) -> int:
|
|
24
|
+
return x * 5
|
|
25
|
+
|
|
26
|
+
@go
|
|
27
|
+
def concurrent_function(x: int) -> int:
|
|
28
|
+
return x * 6
|
|
29
|
+
|
|
30
|
+
@kotlin
|
|
31
|
+
def android_function(x: int) -> int:
|
|
32
|
+
return x * 7
|
|
33
|
+
|
|
34
|
+
The analyzer reads these decorators from the AST to determine which native
|
|
35
|
+
backend each function should be compiled to. At Python runtime (when running
|
|
36
|
+
the source directly for testing), these are identity decorators — they just
|
|
37
|
+
return the function unchanged.
|
|
38
|
+
|
|
39
|
+
Backend strengths (from web research):
|
|
40
|
+
@rust — memory safety without GC, fearless concurrency, systems, security-sensitive
|
|
41
|
+
@cpp — C++ ecosystem (CUDA, OpenCV, game engines), template metaprogramming, performance-critical
|
|
42
|
+
@dart — stays in Dart (Flutter UI, async event loop, state management, cross-platform)
|
|
43
|
+
@csharp — .NET ecosystem, enterprise logic, game dev (Unity), NativeAOT to C ABI
|
|
44
|
+
@zig — no runtime, fast compilation, manual memory control, embedded, C replacement
|
|
45
|
+
@go — goroutine concurrency, network services, cloud infrastructure, microservices
|
|
46
|
+
@kotlin — Android-native, Kotlin Multiplatform, JVM backend, coroutines, null-safety
|
|
47
|
+
"""
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
from typing import TypeVar, Callable
|
|
51
|
+
|
|
52
|
+
T = TypeVar("T")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def rust(func: T) -> T:
|
|
56
|
+
"""Mark a function for compilation to Rust. No-op at Python runtime."""
|
|
57
|
+
return func
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cpp(func: T) -> T:
|
|
61
|
+
"""Mark a function for compilation to C++. No-op at Python runtime."""
|
|
62
|
+
return func
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def dart(func: T) -> T:
|
|
66
|
+
"""Mark a function to stay in Dart (not compiled to native). No-op at Python runtime."""
|
|
67
|
+
return func
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def csharp(func: T) -> T:
|
|
71
|
+
"""Mark a function for compilation to C# via .NET NativeAOT. No-op at Python runtime."""
|
|
72
|
+
return func
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def zig(func: T) -> T:
|
|
76
|
+
"""Mark a function for compilation to Zig. No-op at Python runtime."""
|
|
77
|
+
return func
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def go(func: T) -> T:
|
|
81
|
+
"""Mark a function for compilation to Go (cgo shared library). No-op at Python runtime."""
|
|
82
|
+
return func
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def kotlin(func: T) -> T:
|
|
86
|
+
"""Mark a function for compilation to Kotlin/Native. No-op at Python runtime."""
|
|
87
|
+
return func
|