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,258 @@
|
|
|
1
|
+
"""Desktop entry point for {app_name} — thin Rust shell.
|
|
2
|
+
|
|
3
|
+
The shell owns only the window and the message loop. All rendering is
|
|
4
|
+
delegated to the C++ UI layer; all computation to the C++ core layer.
|
|
5
|
+
|
|
6
|
+
Rust — window, event loop, bounded state, input handling
|
|
7
|
+
C++ — ui_draw_frame(...) rendering, factorial/fibonacci/... compute
|
|
8
|
+
|
|
9
|
+
Memory model (Rust): fixed-size scalar state, no collections that grow,
|
|
10
|
+
no per-frame heap allocation, ownership handles all cleanup.
|
|
11
|
+
|
|
12
|
+
Build: python build.py --run
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
from app.main import (
|
|
16
|
+
mem_clamp_input, mem_step_up, mem_step_down, mem_normalize_input,
|
|
17
|
+
mem_initial_input, mem_initial_result, mem_initial_op, mem_next_op,
|
|
18
|
+
factorial, fibonacci, is_prime, gcd,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# File-scope Win32 FFI declarations for the Rust shell.
|
|
22
|
+
ge_preamble("rust", """
|
|
23
|
+
// Note: warnings are suppressed by the build (rustc -A warnings) because
|
|
24
|
+
// this file is generated; inner attributes cannot appear mid-file.
|
|
25
|
+
|
|
26
|
+
// === Win32 type aliases ===
|
|
27
|
+
type HWND = *mut std::ffi::c_void;
|
|
28
|
+
type HDC = *mut std::ffi::c_void;
|
|
29
|
+
type HINSTANCE = *mut std::ffi::c_void;
|
|
30
|
+
type HMENU = *mut std::ffi::c_void;
|
|
31
|
+
type HCURSOR = *mut std::ffi::c_void;
|
|
32
|
+
type HICON = *mut std::ffi::c_void;
|
|
33
|
+
type WPARAM = usize;
|
|
34
|
+
type LPARAM = isize;
|
|
35
|
+
type LRESULT = isize;
|
|
36
|
+
type UINT = u32;
|
|
37
|
+
type DWORD = u32;
|
|
38
|
+
type LONG = i32;
|
|
39
|
+
type BOOL = i32;
|
|
40
|
+
type BYTE = u8;
|
|
41
|
+
type ATOM = u16;
|
|
42
|
+
|
|
43
|
+
#[repr(C)]
|
|
44
|
+
struct RECT { left: LONG, top: LONG, right: LONG, bottom: LONG }
|
|
45
|
+
#[repr(C)]
|
|
46
|
+
struct POINT { x: LONG, y: LONG }
|
|
47
|
+
#[repr(C)]
|
|
48
|
+
struct MSG {
|
|
49
|
+
hwnd: HWND,
|
|
50
|
+
message: UINT,
|
|
51
|
+
wParam: WPARAM,
|
|
52
|
+
lParam: LPARAM,
|
|
53
|
+
time: DWORD,
|
|
54
|
+
pt: POINT,
|
|
55
|
+
}
|
|
56
|
+
#[repr(C)]
|
|
57
|
+
struct PAINTSTRUCT {
|
|
58
|
+
hdc: HDC,
|
|
59
|
+
fErase: BOOL,
|
|
60
|
+
rcPaint: RECT,
|
|
61
|
+
fRestore: BOOL,
|
|
62
|
+
fIncUpdate: BOOL,
|
|
63
|
+
rgbReserved: [BYTE; 32],
|
|
64
|
+
}
|
|
65
|
+
#[repr(C)]
|
|
66
|
+
struct WNDCLASSEXA {
|
|
67
|
+
cbSize: UINT,
|
|
68
|
+
style: UINT,
|
|
69
|
+
lpfnWndProc: Option<unsafe extern "system" fn(HWND, UINT, WPARAM, LPARAM) -> LRESULT>,
|
|
70
|
+
cbClsExtra: i32,
|
|
71
|
+
cbWndExtra: i32,
|
|
72
|
+
hInstance: HINSTANCE,
|
|
73
|
+
hIcon: HICON,
|
|
74
|
+
hCursor: HCURSOR,
|
|
75
|
+
hbrBackground: *mut std::ffi::c_void,
|
|
76
|
+
lpszMenuName: *const u8,
|
|
77
|
+
lpszClassName: *const u8,
|
|
78
|
+
hIconSm: HICON,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// === Win32 constants ===
|
|
82
|
+
const CS_HREDRAW: u32 = 2;
|
|
83
|
+
const CS_VREDRAW: u32 = 1;
|
|
84
|
+
const CW_USEDEFAULT: i32 = -2147483648;
|
|
85
|
+
const SW_SHOWDEFAULT: i32 = 10;
|
|
86
|
+
const WM_PAINT: u32 = 15;
|
|
87
|
+
const WM_DESTROY: u32 = 2;
|
|
88
|
+
const WM_KEYDOWN: u32 = 256;
|
|
89
|
+
const WM_CLOSE: u32 = 16;
|
|
90
|
+
const WM_ERASEBKGND: u32 = 20;
|
|
91
|
+
const VK_ESCAPE: i32 = 27;
|
|
92
|
+
const VK_RETURN: i32 = 13;
|
|
93
|
+
const VK_UP: i32 = 0x26;
|
|
94
|
+
const VK_DOWN: i32 = 0x28;
|
|
95
|
+
const VK_F: i32 = 0x46;
|
|
96
|
+
const VK_P: i32 = 0x50;
|
|
97
|
+
const VK_G: i32 = 0x47;
|
|
98
|
+
const WS_OVERLAPPEDWINDOW: u32 = 13565952;
|
|
99
|
+
|
|
100
|
+
// === Win32 FFI ===
|
|
101
|
+
extern "system" {
|
|
102
|
+
fn BeginPaint(hwnd: HWND, lpPaint: *mut PAINTSTRUCT) -> HDC;
|
|
103
|
+
fn EndPaint(hwnd: HWND, lpPaint: *const PAINTSTRUCT) -> BOOL;
|
|
104
|
+
fn GetClientRect(hwnd: HWND, lpRect: *mut RECT) -> BOOL;
|
|
105
|
+
fn InvalidateRect(hwnd: HWND, lpRect: *const RECT, bErase: BOOL) -> BOOL;
|
|
106
|
+
fn RegisterClassExA(lpwcx: *const WNDCLASSEXA) -> ATOM;
|
|
107
|
+
fn CreateWindowExA(dwExStyle: u32, lpClassName: *const u8, lpWindowName: *const u8,
|
|
108
|
+
dwStyle: u32, x: i32, y: i32, nWidth: i32, nHeight: i32,
|
|
109
|
+
hWndParent: HWND, hMenu: HMENU, hInstance: HINSTANCE,
|
|
110
|
+
lpParam: *mut std::ffi::c_void) -> HWND;
|
|
111
|
+
fn ShowWindow(hwnd: HWND, nCmdShow: i32) -> BOOL;
|
|
112
|
+
fn UpdateWindow(hwnd: HWND) -> BOOL;
|
|
113
|
+
fn GetMessageA(lpMsg: *mut MSG, hwnd: HWND, wMsgFilterMin: u32, wMsgFilterMax: u32) -> BOOL;
|
|
114
|
+
fn TranslateMessage(lpMsg: *const MSG) -> BOOL;
|
|
115
|
+
fn DispatchMessageA(lpMsg: *const MSG) -> LRESULT;
|
|
116
|
+
fn DefWindowProcA(hwnd: HWND, msg: u32, wParam: WPARAM, lParam: LPARAM) -> LRESULT;
|
|
117
|
+
fn PostQuitMessage(nExitCode: i32);
|
|
118
|
+
fn DestroyWindow(hwnd: HWND) -> BOOL;
|
|
119
|
+
fn GetModuleHandleA(lpModuleName: *const u8) -> HINSTANCE;
|
|
120
|
+
fn LoadCursorA(hInstance: HINSTANCE, lpCursorName: *const u8) -> HCURSOR;
|
|
121
|
+
fn LoadIconA(hInstance: HINSTANCE, lpIconName: *const u8) -> HICON;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// The C++ functions this shell calls are declared automatically by
|
|
125
|
+
// `ge build` (mixed-backend link step), so no hand-written extern block
|
|
126
|
+
// is needed here.
|
|
127
|
+
|
|
128
|
+
// === Bounded state ===
|
|
129
|
+
static mut G_HWND: HWND = std::ptr::null_mut();
|
|
130
|
+
static mut G_INPUT: i64 = 10;
|
|
131
|
+
static mut G_RESULT: i64 = 0;
|
|
132
|
+
static mut G_OP: i64 = 0;
|
|
133
|
+
|
|
134
|
+
fn run_op(op: i64, input: i64) -> i64 {
|
|
135
|
+
unsafe {
|
|
136
|
+
match op {
|
|
137
|
+
0 => factorial(input),
|
|
138
|
+
1 => fibonacci(input),
|
|
139
|
+
2 => is_prime(input),
|
|
140
|
+
3 => gcd(input, input / 2 + 1),
|
|
141
|
+
_ => 0,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
|
147
|
+
unsafe {
|
|
148
|
+
match msg {
|
|
149
|
+
WM_PAINT => {
|
|
150
|
+
let mut ps: PAINTSTRUCT = std::mem::zeroed();
|
|
151
|
+
let hdc = BeginPaint(hwnd, &mut ps);
|
|
152
|
+
let mut rc: RECT = std::mem::zeroed();
|
|
153
|
+
GetClientRect(hwnd, &mut rc);
|
|
154
|
+
let w = (rc.right - rc.left) as i64;
|
|
155
|
+
let h = (rc.bottom - rc.top) as i64;
|
|
156
|
+
ui_draw_frame(hdc as i64, w, h, G_INPUT, G_RESULT, G_OP);
|
|
157
|
+
EndPaint(hwnd, &ps);
|
|
158
|
+
0
|
|
159
|
+
}
|
|
160
|
+
WM_ERASEBKGND => 1,
|
|
161
|
+
WM_KEYDOWN => {
|
|
162
|
+
let k = wparam as i32;
|
|
163
|
+
if k == VK_ESCAPE {
|
|
164
|
+
DestroyWindow(hwnd);
|
|
165
|
+
} else if k == VK_RETURN {
|
|
166
|
+
G_RESULT = run_op(G_OP, G_INPUT);
|
|
167
|
+
InvalidateRect(hwnd, std::ptr::null(), 0);
|
|
168
|
+
} else if k == VK_UP {
|
|
169
|
+
G_INPUT = mem_step_up(G_INPUT);
|
|
170
|
+
InvalidateRect(hwnd, std::ptr::null(), 0);
|
|
171
|
+
} else if k == VK_DOWN {
|
|
172
|
+
G_INPUT = mem_step_down(G_INPUT);
|
|
173
|
+
InvalidateRect(hwnd, std::ptr::null(), 0);
|
|
174
|
+
} else if k == VK_F {
|
|
175
|
+
G_OP = 1;
|
|
176
|
+
G_RESULT = run_op(1, G_INPUT);
|
|
177
|
+
InvalidateRect(hwnd, std::ptr::null(), 0);
|
|
178
|
+
} else if k == VK_P {
|
|
179
|
+
G_OP = 2;
|
|
180
|
+
G_RESULT = run_op(2, G_INPUT);
|
|
181
|
+
InvalidateRect(hwnd, std::ptr::null(), 0);
|
|
182
|
+
} else if k == VK_G {
|
|
183
|
+
G_OP = 3;
|
|
184
|
+
G_RESULT = run_op(3, G_INPUT);
|
|
185
|
+
InvalidateRect(hwnd, std::ptr::null(), 0);
|
|
186
|
+
}
|
|
187
|
+
0
|
|
188
|
+
}
|
|
189
|
+
WM_CLOSE => { DestroyWindow(hwnd); 0 }
|
|
190
|
+
WM_DESTROY => { PostQuitMessage(0); 0 }
|
|
191
|
+
_ => DefWindowProcA(hwnd, msg, wparam, lparam),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
""")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@rust
|
|
199
|
+
def desktop_bootstrap() -> int:
|
|
200
|
+
"""Initialize bounded state from the memory layer."""
|
|
201
|
+
ge_inline("rust", """
|
|
202
|
+
unsafe {
|
|
203
|
+
G_INPUT = mem_normalize_input(mem_initial_input());
|
|
204
|
+
G_RESULT = mem_initial_result();
|
|
205
|
+
G_OP = mem_initial_op();
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
""")
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@rust
|
|
213
|
+
def desktop_run() -> int:
|
|
214
|
+
"""Create the window and run the message loop."""
|
|
215
|
+
ge_inline("rust", """
|
|
216
|
+
unsafe {
|
|
217
|
+
let class_name = b"GEAppWnd\0".as_ptr();
|
|
218
|
+
let hinst = GetModuleHandleA(std::ptr::null());
|
|
219
|
+
let wc = WNDCLASSEXA {
|
|
220
|
+
cbSize: std::mem::size_of::<WNDCLASSEXA>() as u32,
|
|
221
|
+
style: CS_HREDRAW | CS_VREDRAW,
|
|
222
|
+
lpfnWndProc: Some(wnd_proc),
|
|
223
|
+
cbClsExtra: 0,
|
|
224
|
+
cbWndExtra: 0,
|
|
225
|
+
hInstance: hinst,
|
|
226
|
+
hIcon: LoadIconA(std::ptr::null_mut(), std::ptr::null()),
|
|
227
|
+
hCursor: LoadCursorA(std::ptr::null_mut(), std::ptr::null()),
|
|
228
|
+
hbrBackground: std::ptr::null_mut(),
|
|
229
|
+
lpszMenuName: std::ptr::null(),
|
|
230
|
+
lpszClassName: class_name,
|
|
231
|
+
hIconSm: std::ptr::null_mut(),
|
|
232
|
+
};
|
|
233
|
+
if RegisterClassExA(&wc) == 0 { return 1; }
|
|
234
|
+
|
|
235
|
+
let title = b"{app_name} - Rust + C++ Desktop\0".as_ptr();
|
|
236
|
+
G_HWND = CreateWindowExA(0, class_name, title,
|
|
237
|
+
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1040, 680,
|
|
238
|
+
std::ptr::null_mut(), std::ptr::null_mut(), hinst, std::ptr::null_mut());
|
|
239
|
+
if G_HWND.is_null() { return 1; }
|
|
240
|
+
|
|
241
|
+
ShowWindow(G_HWND, SW_SHOWDEFAULT);
|
|
242
|
+
UpdateWindow(G_HWND);
|
|
243
|
+
|
|
244
|
+
let mut msg: MSG = std::mem::zeroed();
|
|
245
|
+
while GetMessageA(&mut msg, std::ptr::null_mut(), 0, 0) > 0 {
|
|
246
|
+
TranslateMessage(&msg);
|
|
247
|
+
DispatchMessageA(&msg);
|
|
248
|
+
}
|
|
249
|
+
msg.wParam as i64
|
|
250
|
+
}
|
|
251
|
+
""")
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def main() -> int:
|
|
256
|
+
"""Entry point: bootstrap state, then run the window loop."""
|
|
257
|
+
desktop_bootstrap()
|
|
258
|
+
return desktop_run()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# GE project configuration for {app_name}
|
|
2
|
+
[project]
|
|
3
|
+
name = "{app_name}"
|
|
4
|
+
version = "0.1.0"
|
|
5
|
+
description = "Native desktop app — Rust shell + C++ core + C++ UI"
|
|
6
|
+
template = "desktop-gui"
|
|
7
|
+
|
|
8
|
+
[build]
|
|
9
|
+
platforms = ["desktop"]
|
|
10
|
+
languages = ["rust", "cpp"]
|
|
11
|
+
|
|
12
|
+
[layers]
|
|
13
|
+
memory = "app/memory" # Rust
|
|
14
|
+
core = "app/core" # C++
|
|
15
|
+
ui = "app/ui" # C++
|
|
16
|
+
entry = "desktop/main.ge.py"
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Import hook for .ge.py modules.
|
|
2
|
+
|
|
3
|
+
Python cannot import `app/core/add.ge.py` as `app.core.add`, so this
|
|
4
|
+
module installs a meta path finder that maps dotted names onto .ge.py
|
|
5
|
+
files. After install(), normal imports just work, with Python handling
|
|
6
|
+
ordering, caching, and circular-import semantics:
|
|
7
|
+
|
|
8
|
+
from ge_loader import install
|
|
9
|
+
install()
|
|
10
|
+
from app.core.factorial import factorial
|
|
11
|
+
"""
|
|
12
|
+
import importlib.abc
|
|
13
|
+
import importlib.util
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
ROOT = Path(__file__).parent.parent
|
|
18
|
+
_installed = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _stub_preamble(backend, code): # noqa: ARG001
|
|
22
|
+
"""No-op stand-in for the GE ge_preamble intrinsic."""
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _stub_value(*args, **kwargs): # noqa: ARG001
|
|
27
|
+
"""No-op stand-in for ge_inline / ge_raw; returns int 0."""
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GeFinder(importlib.abc.MetaPathFinder, importlib.abc.Loader):
|
|
32
|
+
"""Resolve `pkg.mod` to `pkg/mod.ge.py` under the project root."""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _ge_path(parts: list[str]) -> Path:
|
|
36
|
+
return ROOT.joinpath(*parts[:-1]) / (parts[-1] + ".ge.py")
|
|
37
|
+
|
|
38
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
39
|
+
parts = fullname.split(".")
|
|
40
|
+
ge_path = self._ge_path(parts)
|
|
41
|
+
if ge_path.is_file():
|
|
42
|
+
return importlib.util.spec_from_loader(fullname, self)
|
|
43
|
+
pkg_dir = ROOT.joinpath(*parts)
|
|
44
|
+
init = pkg_dir / "__init__.py"
|
|
45
|
+
if init.is_file():
|
|
46
|
+
return importlib.util.spec_from_file_location(
|
|
47
|
+
fullname, init, submodule_search_locations=[str(pkg_dir)])
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def create_module(self, spec):
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def exec_module(self, module):
|
|
54
|
+
parts = module.__name__.split(".")
|
|
55
|
+
ge_path = self._ge_path(parts)
|
|
56
|
+
code = ge_path.read_text(encoding="utf-8")
|
|
57
|
+
# GE compiler intrinsics are not Python builtins; provide no-op
|
|
58
|
+
# stubs so the module can be imported for unit testing.
|
|
59
|
+
ns = module.__dict__
|
|
60
|
+
ns.setdefault("ge_preamble", _stub_preamble)
|
|
61
|
+
ns.setdefault("ge_inline", _stub_value)
|
|
62
|
+
ns.setdefault("ge_raw", _stub_value)
|
|
63
|
+
exec(compile(code, str(ge_path), "exec"), ns)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def install() -> None:
|
|
67
|
+
"""Install the .ge.py import hook (idempotent)."""
|
|
68
|
+
global _installed
|
|
69
|
+
if _installed:
|
|
70
|
+
return
|
|
71
|
+
if not any(isinstance(f, GeFinder) for f in sys.meta_path):
|
|
72
|
+
sys.meta_path.insert(0, GeFinder())
|
|
73
|
+
root = str(ROOT)
|
|
74
|
+
if root not in sys.path:
|
|
75
|
+
sys.path.insert(0, root)
|
|
76
|
+
_installed = True
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
# .ge.py modules are importable now.
|
|
19
|
+
from app.core.add import add # noqa: E402
|
|
20
|
+
from app.core.multiply import multiply # noqa: E402
|
|
21
|
+
from app.core.factorial import factorial # noqa: E402
|
|
22
|
+
from app.core.fibonacci import fibonacci # noqa: E402
|
|
23
|
+
from app.core.is_prime import is_prime # noqa: E402
|
|
24
|
+
from app.core.gcd import gcd # noqa: E402
|
|
25
|
+
from app.core.power import power # noqa: E402
|
|
26
|
+
from app.memory.limits import ( # noqa: E402
|
|
27
|
+
mem_input_min, mem_input_max, mem_clamp_input, mem_step_up, mem_step_down,
|
|
28
|
+
)
|
|
29
|
+
from app.memory.buffer import ( # noqa: E402
|
|
30
|
+
mem_buffer_capacity, mem_digits_capacity, mem_state_slots,
|
|
31
|
+
)
|
|
32
|
+
from app.memory.state import ( # noqa: E402
|
|
33
|
+
mem_initial_input, mem_initial_result, mem_initial_op,
|
|
34
|
+
mem_op_count, mem_next_op, mem_normalize_input,
|
|
35
|
+
)
|
|
36
|
+
from app.ui.theme import ui_color_bg, ui_color_accent, ui_font_body, ui_radius # noqa: E402
|
|
37
|
+
from app.ui.layout import ( # noqa: E402
|
|
38
|
+
ui_card_width, ui_card_height, ui_sidebar_width, ui_content_x,
|
|
39
|
+
ui_center_x, ui_row_y,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestCoreFunctions(unittest.TestCase):
|
|
44
|
+
"""C++ core layer - computation."""
|
|
45
|
+
|
|
46
|
+
def test_add(self):
|
|
47
|
+
self.assertEqual(add(3, 4), 7)
|
|
48
|
+
self.assertEqual(add(-1, 1), 0)
|
|
49
|
+
|
|
50
|
+
def test_multiply(self):
|
|
51
|
+
self.assertEqual(multiply(3, 4), 12)
|
|
52
|
+
self.assertEqual(multiply(0, 5), 0)
|
|
53
|
+
|
|
54
|
+
def test_factorial(self):
|
|
55
|
+
self.assertEqual(factorial(0), 1)
|
|
56
|
+
self.assertEqual(factorial(5), 120)
|
|
57
|
+
self.assertEqual(factorial(10), 3628800)
|
|
58
|
+
|
|
59
|
+
def test_fibonacci(self):
|
|
60
|
+
self.assertEqual(fibonacci(0), 0)
|
|
61
|
+
self.assertEqual(fibonacci(1), 1)
|
|
62
|
+
self.assertEqual(fibonacci(10), 55)
|
|
63
|
+
|
|
64
|
+
def test_is_prime(self):
|
|
65
|
+
self.assertEqual(is_prime(2), 1)
|
|
66
|
+
self.assertEqual(is_prime(3), 1)
|
|
67
|
+
self.assertEqual(is_prime(4), 0)
|
|
68
|
+
self.assertEqual(is_prime(17), 1)
|
|
69
|
+
self.assertEqual(is_prime(100), 0)
|
|
70
|
+
|
|
71
|
+
def test_gcd(self):
|
|
72
|
+
self.assertEqual(gcd(12, 8), 4)
|
|
73
|
+
self.assertEqual(gcd(17, 5), 1)
|
|
74
|
+
self.assertEqual(gcd(100, 50), 50)
|
|
75
|
+
|
|
76
|
+
def test_power(self):
|
|
77
|
+
self.assertEqual(power(2, 0), 1)
|
|
78
|
+
self.assertEqual(power(2, 10), 1024)
|
|
79
|
+
self.assertEqual(power(3, 3), 27)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestMemoryLayer(unittest.TestCase):
|
|
83
|
+
"""Rust memory layer - bounds and state."""
|
|
84
|
+
|
|
85
|
+
def test_clamp_input(self):
|
|
86
|
+
self.assertEqual(mem_clamp_input(-5), mem_input_min())
|
|
87
|
+
self.assertEqual(mem_clamp_input(999), mem_input_max())
|
|
88
|
+
self.assertEqual(mem_clamp_input(7), 7)
|
|
89
|
+
|
|
90
|
+
def test_step_up_down(self):
|
|
91
|
+
self.assertEqual(mem_step_up(1), 2)
|
|
92
|
+
self.assertEqual(mem_step_down(1), 0)
|
|
93
|
+
self.assertEqual(mem_step_up(mem_input_max()),
|
|
94
|
+
mem_input_max())
|
|
95
|
+
self.assertEqual(mem_step_down(mem_input_min()),
|
|
96
|
+
mem_input_min())
|
|
97
|
+
|
|
98
|
+
def test_factorial_fits_in_i64(self):
|
|
99
|
+
# mem_input_max() must keep factorial within i64 range
|
|
100
|
+
self.assertLess(factorial(mem_input_max()),
|
|
101
|
+
2 ** 63)
|
|
102
|
+
|
|
103
|
+
def test_state_defaults(self):
|
|
104
|
+
self.assertEqual(mem_initial_result(), 0)
|
|
105
|
+
self.assertEqual(mem_initial_op(), 0)
|
|
106
|
+
self.assertIsInstance(mem_initial_input(), int)
|
|
107
|
+
|
|
108
|
+
def test_op_cycle(self):
|
|
109
|
+
self.assertEqual(mem_next_op(mem_op_count() - 1), 0)
|
|
110
|
+
self.assertEqual(mem_next_op(0), 1)
|
|
111
|
+
|
|
112
|
+
def test_normalize(self):
|
|
113
|
+
self.assertEqual(mem_normalize_input(999), mem_input_max())
|
|
114
|
+
|
|
115
|
+
def test_buffers_bounded(self):
|
|
116
|
+
self.assertGreater(mem_buffer_capacity(),
|
|
117
|
+
mem_digits_capacity())
|
|
118
|
+
self.assertGreater(mem_state_slots(), 0)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestUILayer(unittest.TestCase):
|
|
122
|
+
"""C++ UI layer - design tokens and layout math."""
|
|
123
|
+
|
|
124
|
+
def test_theme_tokens(self):
|
|
125
|
+
self.assertIsInstance(ui_color_bg(), int)
|
|
126
|
+
self.assertIsInstance(ui_color_accent(), int)
|
|
127
|
+
self.assertGreater(ui_font_body(), 0)
|
|
128
|
+
self.assertGreater(ui_radius(), 0)
|
|
129
|
+
|
|
130
|
+
def test_layout_math(self):
|
|
131
|
+
self.assertGreater(ui_card_width(1200), 0)
|
|
132
|
+
self.assertGreater(ui_content_x(), ui_sidebar_width())
|
|
133
|
+
self.assertGreater(ui_card_height(800), 0)
|
|
134
|
+
|
|
135
|
+
def test_centering(self):
|
|
136
|
+
self.assertEqual(ui_center_x(0, 100, 20), 40)
|
|
137
|
+
|
|
138
|
+
def test_rows_increase(self):
|
|
139
|
+
self.assertGreater(ui_row_y(2), ui_row_y(1))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class TestProjectStructure(unittest.TestCase):
|
|
143
|
+
"""Layered architecture is present on disk."""
|
|
144
|
+
|
|
145
|
+
def test_layers_exist(self):
|
|
146
|
+
for layer in ["memory", "core", "ui"]:
|
|
147
|
+
self.assertTrue((ROOT / "app" / layer).is_dir(),
|
|
148
|
+
"app/" + layer + "/ should exist")
|
|
149
|
+
|
|
150
|
+
def test_one_function_per_core_file(self):
|
|
151
|
+
core = ROOT / "app" / "core"
|
|
152
|
+
for f in ["add.ge.py", "multiply.ge.py", "factorial.ge.py",
|
|
153
|
+
"fibonacci.ge.py", "is_prime.ge.py", "gcd.ge.py", "power.ge.py"]:
|
|
154
|
+
self.assertTrue((core / f).exists(), "app/core/" + f + " should exist")
|
|
155
|
+
|
|
156
|
+
def test_ui_files(self):
|
|
157
|
+
ui = ROOT / "app" / "ui"
|
|
158
|
+
for f in ["theme.ge.py", "layout.ge.py", "widgets.ge.py", "render.ge.py"]:
|
|
159
|
+
self.assertTrue((ui / f).exists(), "app/ui/" + f + " should exist")
|
|
160
|
+
|
|
161
|
+
def test_memory_files(self):
|
|
162
|
+
mem = ROOT / "app" / "memory"
|
|
163
|
+
for f in ["limits.ge.py", "buffer.ge.py", "state.ge.py"]:
|
|
164
|
+
self.assertTrue((mem / f).exists(), "app/memory/" + f + " should exist")
|
|
165
|
+
|
|
166
|
+
def test_entry_and_config(self):
|
|
167
|
+
# the build is `ge build desktop/main.ge.py` — no project build script
|
|
168
|
+
self.assertTrue((ROOT / "desktop" / "main.ge.py").exists())
|
|
169
|
+
self.assertTrue((ROOT / "ge.toml").exists())
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
unittest.main()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# {app_name}
|
|
2
|
+
|
|
3
|
+
Full-stack web application built with the GE programming language.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{app_name}/
|
|
9
|
+
app/
|
|
10
|
+
memory/ Rust — request bounds, buffer capacities
|
|
11
|
+
limits.ge.py
|
|
12
|
+
buffer.ge.py
|
|
13
|
+
core/ Rust — computation, one function per file
|
|
14
|
+
add.ge.py multiply.ge.py factorial.ge.py
|
|
15
|
+
fibonacci.ge.py is_prime.ge.py
|
|
16
|
+
main.ge.py aggregator
|
|
17
|
+
web/
|
|
18
|
+
server.ge.py Rust backend (HTTP API + static SPA)
|
|
19
|
+
frontend/ React 19 + TypeScript + Vite (generated, then yours)
|
|
20
|
+
src/App.tsx
|
|
21
|
+
src/api/client.ts
|
|
22
|
+
src/components/Ge*.tsx one component per file
|
|
23
|
+
ui/
|
|
24
|
+
main.ge.ui declarative UI — source of truth for the frontend
|
|
25
|
+
tests/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Layers
|
|
29
|
+
|
|
30
|
+
| Layer | Language | Responsibility |
|
|
31
|
+
|-------|----------|----------------|
|
|
32
|
+
| Frontend | React + TypeScript | UI, calls `/api/call` |
|
|
33
|
+
| Backend | Rust | HTTP, dispatch, bounds enforcement |
|
|
34
|
+
| Memory | Rust | clamp inputs, cap buffers |
|
|
35
|
+
| Core | Rust | pure computation |
|
|
36
|
+
|
|
37
|
+
### Data flow
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
browser -> POST /api/call {name, args}
|
|
41
|
+
|
|
|
42
|
+
v
|
|
43
|
+
Rust backend -> mem_clamp_arg(args) -> core function
|
|
44
|
+
|
|
|
45
|
+
v
|
|
46
|
+
JSON {ok, value} -> React state -> GeText/GeValue
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Build output layout
|
|
50
|
+
|
|
51
|
+
Everything lands under `build/`, bucketed by platform so a multi-target
|
|
52
|
+
project stays readable:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
build/
|
|
56
|
+
desktop/ native desktop build
|
|
57
|
+
backend/ generated native sources
|
|
58
|
+
obj/ object files
|
|
59
|
+
bin/ executables
|
|
60
|
+
web/ web build
|
|
61
|
+
backend/ generated Rust backend
|
|
62
|
+
bin/ server executable
|
|
63
|
+
mobile/ Flutter project
|
|
64
|
+
backend/ ui/ lib/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Build
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
ge build web/server.ge.py --run # compile the Rust backend
|
|
71
|
+
ge react ui/main.ge.ui --app-name {app_name} # generate the React frontend
|
|
72
|
+
cd web/frontend && npm install && npm run build # bundle the SPA
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`ge build` compiles the backend straight from the GE sources. `ge react`
|
|
76
|
+
generates the frontend from the UI DSL, and only writes files that do not
|
|
77
|
+
already exist unless you pass `--force`.
|
|
78
|
+
|
|
79
|
+
## Frontend workflow
|
|
80
|
+
|
|
81
|
+
`ui/main.ge.ui` is the source of truth for the layout:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
ge react ui/main.ge.ui --app-name {app_name} # regenerate (keeps edits)
|
|
85
|
+
ge react ui/main.ge.ui --force # regenerate, overwrite
|
|
86
|
+
cd web/frontend && npm run dev # dev server, proxies /api
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Files under `web/frontend/src` are ordinary React/TypeScript. Edit them
|
|
90
|
+
freely: regeneration only writes files that do not exist unless `--force`.
|
|
91
|
+
|
|
92
|
+
## TypeScript source support
|
|
93
|
+
|
|
94
|
+
Any module can be written in TypeScript flavour instead of Python flavour.
|
|
95
|
+
Both lower to the same IR, so both compile to the same native code:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
app/core/add.ge.py Python flavour
|
|
99
|
+
app/core/add.ts.ge.py TypeScript flavour (same file name, .ts.ge)
|
|
100
|
+
app/core/add.ge.ts TypeScript flavour (alternate extension)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
TypeScript flavour example:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export function add(a: number, b: number): number {
|
|
107
|
+
return a + b;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Test
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python -m unittest discover tests
|
|
115
|
+
```
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Core function: factorial. Compiled to Rust."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from pyeffic.backends import rust
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@rust
|
|
7
|
+
def factorial(n: int) -> int:
|
|
8
|
+
"""Compute factorial of n (n is clamped by the memory layer)."""
|
|
9
|
+
if n <= 1:
|
|
10
|
+
return 1
|
|
11
|
+
result: int = 1
|
|
12
|
+
i: int = 2
|
|
13
|
+
while i <= n:
|
|
14
|
+
result = result * i
|
|
15
|
+
i = i + 1
|
|
16
|
+
return result
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Core function: fibonacci. Compiled to Rust."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from pyeffic.backends import rust
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@rust
|
|
7
|
+
def fibonacci(n: int) -> int:
|
|
8
|
+
"""Compute the nth Fibonacci number iteratively."""
|
|
9
|
+
if n <= 0:
|
|
10
|
+
return 0
|
|
11
|
+
if n == 1:
|
|
12
|
+
return 1
|
|
13
|
+
a: int = 0
|
|
14
|
+
b: int = 1
|
|
15
|
+
i: int = 2
|
|
16
|
+
while i <= n:
|
|
17
|
+
temp: int = a + b
|
|
18
|
+
a = b
|
|
19
|
+
b = temp
|
|
20
|
+
i = i + 1
|
|
21
|
+
return b
|