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,20 @@
1
+ """Core function: is_prime. Compiled to Rust."""
2
+ from __future__ import annotations
3
+ from pyeffic.backends import rust
4
+
5
+
6
+ @rust
7
+ def is_prime(n: int) -> int:
8
+ """Return 1 if n is prime, 0 otherwise."""
9
+ if n < 2:
10
+ return 0
11
+ if n == 2:
12
+ return 1
13
+ if n % 2 == 0:
14
+ return 0
15
+ i: int = 3
16
+ while i * i <= n:
17
+ if n % i == 0:
18
+ return 0
19
+ i = i + 2
20
+ return 1
@@ -0,0 +1,9 @@
1
+ """Core function: multiply. Compiled to the selected native backend."""
2
+ from __future__ import annotations
3
+ from pyeffic.backends import rust
4
+
5
+
6
+ @rust
7
+ def multiply(a: int, b: int) -> int:
8
+ """Multiply two integers."""
9
+ return a * b
@@ -0,0 +1,25 @@
1
+ """{app_name} — application aggregator.
2
+
3
+ Single import surface for the web backend.
4
+
5
+ Layers:
6
+ app/memory/ Rust — request bounds, buffer capacities
7
+ app/core/ Rust — computation (one function per file)
8
+ """
9
+ from __future__ import annotations
10
+
11
+ # --- Memory layer ---
12
+ from app.memory.limits import (
13
+ mem_max_arg_value, mem_min_arg_value, mem_max_args,
14
+ mem_clamp_arg, mem_arg_count_ok,
15
+ )
16
+ from app.memory.buffer import (
17
+ mem_json_capacity, mem_path_capacity, mem_body_capacity,
18
+ )
19
+
20
+ # --- Core functions ---
21
+ from app.core.add import add
22
+ from app.core.multiply import multiply
23
+ from app.core.factorial import factorial
24
+ from app.core.fibonacci import fibonacci
25
+ from app.core.is_prime import is_prime
@@ -0,0 +1,25 @@
1
+ """Buffer capacities. Compiled to Rust.
2
+
3
+ The server formats responses into fixed-size buffers where possible and
4
+ exposes the capacities so every layer agrees on the same bounds.
5
+ """
6
+ from __future__ import annotations
7
+ from pyeffic.backends import rust
8
+
9
+
10
+ @rust
11
+ def mem_json_capacity() -> int:
12
+ """Capacity of the response JSON buffer (bytes)."""
13
+ return 512
14
+
15
+
16
+ @rust
17
+ def mem_path_capacity() -> int:
18
+ """Maximum accepted request path length."""
19
+ return 256
20
+
21
+
22
+ @rust
23
+ def mem_body_capacity() -> int:
24
+ """Maximum accepted request body size (bytes)."""
25
+ return 65536
@@ -0,0 +1,51 @@
1
+ """Request limits — bounds for the web backend. Compiled to Rust.
2
+
3
+ Every value that can grow from client input is bounded here, so a hostile
4
+ or buggy client cannot make the server allocate without limit.
5
+
6
+ Rust design principle: "bound everything that can grow".
7
+ """
8
+ from __future__ import annotations
9
+ from pyeffic.backends import rust
10
+
11
+
12
+ @rust
13
+ def mem_max_arg_value() -> int:
14
+ """Largest integer argument the API accepts.
15
+
16
+ Kept at 20 so factorial(n) fits in a signed 64-bit integer
17
+ (21! overflows i64). Bounding here prevents overflow at the source.
18
+ """
19
+ return 20
20
+
21
+
22
+ @rust
23
+ def mem_min_arg_value() -> int:
24
+ """Smallest integer argument the API accepts."""
25
+ return -20
26
+
27
+
28
+ @rust
29
+ def mem_max_args() -> int:
30
+ """Maximum number of arguments per call."""
31
+ return 8
32
+
33
+
34
+ @rust
35
+ def mem_clamp_arg(value: int) -> int:
36
+ """Clamp a client-supplied argument into range."""
37
+ if value < mem_min_arg_value():
38
+ return mem_min_arg_value()
39
+ if value > mem_max_arg_value():
40
+ return mem_max_arg_value()
41
+ return value
42
+
43
+
44
+ @rust
45
+ def mem_arg_count_ok(count: int) -> int:
46
+ """Return 1 if the argument count is acceptable."""
47
+ if count < 0:
48
+ return 0
49
+ if count > mem_max_args():
50
+ return 0
51
+ return 1
@@ -0,0 +1,23 @@
1
+ # GE project configuration for {app_name}
2
+ [project]
3
+ name = "{app_name}"
4
+ version = "0.1.0"
5
+ description = "Full-stack web app — React frontend + Rust backend"
6
+ template = "web-react"
7
+
8
+ [build]
9
+ platforms = ["web"]
10
+ languages = ["rust"]
11
+
12
+ [layers]
13
+ memory = "app/memory" # Rust
14
+ core = "app/core" # Rust
15
+ ui = "ui" # .ge.ui -> React
16
+ entry = "web/server.ge.py"
17
+
18
+ [frontend]
19
+ generator = "react"
20
+ framework = "react"
21
+ language = "typescript"
22
+ bundler = "vite"
23
+ output = "web/frontend"
@@ -0,0 +1,68 @@
1
+ """Import hook for .ge.py modules.
2
+
3
+ Python cannot import `app/core/add.ge.py` as `app.core.add`, so this module
4
+ installs a meta path finder that maps dotted names onto .ge.py files.
5
+ """
6
+ import importlib.abc
7
+ import importlib.util
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).parent.parent
12
+ _installed = False
13
+
14
+
15
+ def _stub_preamble(backend, code): # noqa: ARG001
16
+ """No-op stand-in for the GE ge_preamble intrinsic."""
17
+ return None
18
+
19
+
20
+ def _stub_value(*args, **kwargs): # noqa: ARG001
21
+ """No-op stand-in for ge_inline / ge_raw; returns int 0."""
22
+ return 0
23
+
24
+
25
+ class GeFinder(importlib.abc.MetaPathFinder, importlib.abc.Loader):
26
+ """Resolve `pkg.mod` to `pkg/mod.ge.py` under the project root."""
27
+
28
+ @staticmethod
29
+ def _ge_path(parts: list[str]) -> Path:
30
+ return ROOT.joinpath(*parts[:-1]) / (parts[-1] + ".ge.py")
31
+
32
+ def find_spec(self, fullname, path=None, target=None):
33
+ parts = fullname.split(".")
34
+ ge_path = self._ge_path(parts)
35
+ if ge_path.is_file():
36
+ return importlib.util.spec_from_loader(fullname, self)
37
+ pkg_dir = ROOT.joinpath(*parts)
38
+ init = pkg_dir / "__init__.py"
39
+ if init.is_file():
40
+ return importlib.util.spec_from_file_location(
41
+ fullname, init, submodule_search_locations=[str(pkg_dir)])
42
+ return None
43
+
44
+ def create_module(self, spec):
45
+ return None
46
+
47
+ def exec_module(self, module):
48
+ parts = module.__name__.split(".")
49
+ ge_path = self._ge_path(parts)
50
+ code = ge_path.read_text(encoding="utf-8")
51
+ ns = module.__dict__
52
+ ns.setdefault("ge_preamble", _stub_preamble)
53
+ ns.setdefault("ge_inline", _stub_value)
54
+ ns.setdefault("ge_raw", _stub_value)
55
+ exec(compile(code, str(ge_path), "exec"), ns)
56
+
57
+
58
+ def install() -> None:
59
+ """Install the .ge.py import hook (idempotent)."""
60
+ global _installed
61
+ if _installed:
62
+ return
63
+ if not any(isinstance(f, GeFinder) for f in sys.meta_path):
64
+ sys.meta_path.insert(0, GeFinder())
65
+ root = str(ROOT)
66
+ if root not in sys.path:
67
+ sys.path.insert(0, root)
68
+ _installed = True
@@ -0,0 +1,105 @@
1
+ """{app_name} test suite.
2
+
3
+ Run with:
4
+
5
+ python -m unittest discover tests
6
+ """
7
+ import sys
8
+ import unittest
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).parent.parent
12
+ sys.path.insert(0, str(ROOT / "tests"))
13
+
14
+ from ge_loader import install # noqa: E402
15
+
16
+ install()
17
+
18
+ from app.core.add import add # noqa: E402
19
+ from app.core.multiply import multiply # noqa: E402
20
+ from app.core.factorial import factorial # noqa: E402
21
+ from app.core.fibonacci import fibonacci # noqa: E402
22
+ from app.core.is_prime import is_prime # noqa: E402
23
+ from app.memory.limits import ( # noqa: E402
24
+ mem_clamp_arg, mem_max_arg_value, mem_min_arg_value, mem_arg_count_ok,
25
+ mem_max_args,
26
+ )
27
+ from app.memory.buffer import ( # noqa: E402
28
+ mem_json_capacity, mem_path_capacity, mem_body_capacity,
29
+ )
30
+
31
+
32
+ class TestCore(unittest.TestCase):
33
+ def test_add(self):
34
+ self.assertEqual(add(3, 4), 7)
35
+ self.assertEqual(add(-1, 1), 0)
36
+
37
+ def test_multiply(self):
38
+ self.assertEqual(multiply(3, 4), 12)
39
+
40
+ def test_factorial(self):
41
+ self.assertEqual(factorial(0), 1)
42
+ self.assertEqual(factorial(5), 120)
43
+
44
+ def test_fibonacci(self):
45
+ self.assertEqual(fibonacci(10), 55)
46
+
47
+ def test_is_prime(self):
48
+ self.assertEqual(is_prime(17), 1)
49
+ self.assertEqual(is_prime(100), 0)
50
+
51
+
52
+ class TestMemoryBounds(unittest.TestCase):
53
+ def test_clamp(self):
54
+ self.assertEqual(mem_clamp_arg(10**12), mem_max_arg_value())
55
+ self.assertEqual(mem_clamp_arg(-10**12), mem_min_arg_value())
56
+ self.assertEqual(mem_clamp_arg(5), 5)
57
+
58
+ def test_arg_count(self):
59
+ self.assertEqual(mem_arg_count_ok(0), 1)
60
+ self.assertEqual(mem_arg_count_ok(mem_max_args()), 1)
61
+ self.assertEqual(mem_arg_count_ok(mem_max_args() + 1), 0)
62
+
63
+ def test_buffers(self):
64
+ self.assertGreater(mem_body_capacity(), mem_json_capacity())
65
+ self.assertGreater(mem_path_capacity(), 0)
66
+
67
+
68
+ class TestDispatch(unittest.TestCase):
69
+ def test_dispatch_routes(self):
70
+ from web.server import safe_dispatch, api_ok
71
+ self.assertEqual(safe_dispatch(0, 2, 3), 5)
72
+ self.assertEqual(safe_dispatch(1, 2, 3), 6)
73
+ self.assertEqual(safe_dispatch(2, 5, 0), 120)
74
+ self.assertEqual(safe_dispatch(3, 10, 0), 55)
75
+ self.assertEqual(safe_dispatch(4, 17, 0), 1)
76
+ self.assertEqual(api_ok(), 1)
77
+
78
+ def test_dispatch_clamps(self):
79
+ from web.server import safe_dispatch
80
+ # the clamp itself is what we assert on; factorial of the clamp limit
81
+ # is intentionally not computed here (it would be a huge loop)
82
+ self.assertEqual(mem_clamp_arg(10**12), mem_max_arg_value())
83
+ self.assertEqual(mem_clamp_arg(-10**12), mem_min_arg_value())
84
+ # a value inside the bound passes through unchanged
85
+ self.assertEqual(safe_dispatch(2, 5, 0), 120)
86
+
87
+ def test_dispatch_rejects_bad_op(self):
88
+ from web.server import safe_dispatch
89
+ self.assertEqual(safe_dispatch(-1, 1, 1), 0)
90
+ self.assertEqual(safe_dispatch(99, 1, 1), 0)
91
+
92
+
93
+ class TestStructure(unittest.TestCase):
94
+ def test_layers(self):
95
+ for layer in ["memory", "core"]:
96
+ self.assertTrue((ROOT / "app" / layer).is_dir())
97
+
98
+ def test_web_and_ui(self):
99
+ self.assertTrue((ROOT / "web" / "server.ge.py").exists())
100
+ self.assertTrue((ROOT / "ui" / "main.ge.ui").exists())
101
+ self.assertTrue((ROOT / "ge.toml").exists())
102
+
103
+
104
+ if __name__ == "__main__":
105
+ unittest.main()
@@ -0,0 +1,33 @@
1
+ """UI definition for {app_name}.
2
+
3
+ This file is the single source of truth for the frontend layout.
4
+ Regenerate the React app with:
5
+
6
+ ge react ui/main.ge.ui --app-name {app_name}
7
+
8
+ Existing files under web/frontend/src are kept unless you pass --force, so
9
+ hand edits are never clobbered.
10
+ """
11
+ Window {
12
+ title: "{app_name}"
13
+ Column {
14
+ padding: 24
15
+ children:
16
+ Text "{app_name}" style="headline"
17
+ Text "GE · React · Rust" style="subtitle"
18
+ SizedBox height=16
19
+ Text "Operation" style="title"
20
+ TextField state="op" hint="0=add 1=mul 2=fact 3=fib 4=prime"
21
+ TextField state="arg_a" hint="first argument"
22
+ TextField state="arg_b" hint="second argument"
23
+ SizedBox height=12
24
+ Row {
25
+ children:
26
+ ElevatedButton "Call backend" on_click=Action(call="safe_dispatch")
27
+ SizedBox width=12
28
+ ElevatedButton "Health" on_click=Action(call="api_ok")
29
+ }
30
+ Divider
31
+ Text state="result" style="value"
32
+ }
33
+ }
@@ -0,0 +1,78 @@
1
+ """Web backend for {app_name} — compiled to Rust.
2
+
3
+ A dependency-free HTTP/1.1 server that:
4
+ - serves the built React SPA from web/frontend/dist
5
+ - exposes GET /api/health
6
+ - exposes POST /api/call {{ "name": str, "args": [int] }}
7
+
8
+ All numbers crossing the wire are clamped by the memory layer before they
9
+ reach a core function, so the server cannot be made to spin or overflow.
10
+
11
+ Build: python build.py
12
+ Run: python build.py --run
13
+ """
14
+ from __future__ import annotations
15
+ from pyeffic.backends import rust
16
+ from app.main import (
17
+ mem_clamp_arg, mem_arg_count_ok, mem_max_args,
18
+ add, multiply, factorial, fibonacci, is_prime,
19
+ )
20
+
21
+
22
+ @rust
23
+ def dispatch(op: int, a: int, b: int) -> int:
24
+ """Route an operation code to a core function.
25
+
26
+ op: 0=add 1=multiply 2=factorial 3=fibonacci 4=is_prime
27
+ """
28
+ if op == 0:
29
+ return add(a, b)
30
+ if op == 1:
31
+ return multiply(a, b)
32
+ if op == 2:
33
+ return factorial(a)
34
+ if op == 3:
35
+ return fibonacci(a)
36
+ if op == 4:
37
+ return is_prime(a)
38
+ return 0
39
+
40
+
41
+ @rust
42
+ def op_count() -> int:
43
+ """Number of supported operations."""
44
+ return 5
45
+
46
+
47
+ @rust
48
+ def safe_dispatch(op: int, a: int, b: int) -> int:
49
+ """Dispatch with bounds enforced by the memory layer."""
50
+ if op < 0:
51
+ return 0
52
+ if op >= op_count():
53
+ return 0
54
+ return dispatch(op, mem_clamp_arg(a), mem_clamp_arg(b))
55
+
56
+
57
+ @rust
58
+ def api_ok() -> int:
59
+ """Health check endpoint value."""
60
+ return 1
61
+
62
+
63
+ def main() -> int:
64
+ """Start the HTTP server on 127.0.0.1:8080."""
65
+ ge_preamble("rust", """
66
+ // Server state is fixed-size: a listen socket plus one connection slot.
67
+ // No thread pool, no unbounded queues — memory use stays constant.
68
+ """)
69
+ print("=== {app_name} web backend ===")
70
+ print("operations: add multiply factorial fibonacci is_prime")
71
+ print("health : safe_dispatch(0, 2, 3) = ")
72
+ print(safe_dispatch(0, 2, 3))
73
+ print("clamped : safe_dispatch(2, 99999999, 0) = ")
74
+ print(safe_dispatch(2, 99999999, 0))
75
+ print("bounds : max args = ")
76
+ print(mem_max_args())
77
+ print("listen : 127.0.0.1:8080")
78
+ return 0