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,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,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
|
|
File without changes
|
|
@@ -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"
|
|
File without changes
|
|
@@ -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
|
+
}
|
|
File without changes
|
|
@@ -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
|