zexus 1.6.8 → 1.7.2

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 (177) hide show
  1. package/README.md +12 -5
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/capability_system.py +184 -9
  45. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  46. package/src/zexus/cli/main.py +383 -34
  47. package/src/zexus/cli/zpm.py +1 -1
  48. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  53. package/src/zexus/compiler/bytecode.py +124 -7
  54. package/src/zexus/compiler/compat_runtime.py +6 -2
  55. package/src/zexus/compiler/lexer.py +16 -5
  56. package/src/zexus/compiler/parser.py +108 -7
  57. package/src/zexus/compiler/semantic.py +18 -19
  58. package/src/zexus/compiler/zexus_ast.py +26 -1
  59. package/src/zexus/concurrency_system.py +79 -0
  60. package/src/zexus/config.py +54 -0
  61. package/src/zexus/crypto_bridge.py +244 -8
  62. package/src/zexus/dap/__init__.py +10 -0
  63. package/src/zexus/dap/__main__.py +4 -0
  64. package/src/zexus/dap/dap_server.py +391 -0
  65. package/src/zexus/dap/debug_engine.py +298 -0
  66. package/src/zexus/environment.py +112 -9
  67. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  70. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  71. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  72. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  73. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  74. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  75. package/src/zexus/evaluator/bytecode_compiler.py +457 -37
  76. package/src/zexus/evaluator/core.py +644 -50
  77. package/src/zexus/evaluator/expressions.py +358 -62
  78. package/src/zexus/evaluator/functions.py +458 -20
  79. package/src/zexus/evaluator/resource_limiter.py +4 -4
  80. package/src/zexus/evaluator/statements.py +774 -122
  81. package/src/zexus/evaluator/unified_execution.py +573 -72
  82. package/src/zexus/evaluator/utils.py +14 -2
  83. package/src/zexus/evaluator_original.py +1 -1
  84. package/src/zexus/event_loop.py +186 -0
  85. package/src/zexus/lexer.py +742 -458
  86. package/src/zexus/lsp/__init__.py +1 -1
  87. package/src/zexus/lsp/definition_provider.py +163 -9
  88. package/src/zexus/lsp/server.py +22 -8
  89. package/src/zexus/lsp/symbol_provider.py +182 -9
  90. package/src/zexus/module_cache.py +239 -9
  91. package/src/zexus/module_manager.py +129 -1
  92. package/src/zexus/object.py +76 -6
  93. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  94. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  95. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  96. package/src/zexus/parser/parser.py +1349 -408
  97. package/src/zexus/parser/strategy_context.py +755 -58
  98. package/src/zexus/parser/strategy_structural.py +121 -21
  99. package/src/zexus/persistence.py +15 -1
  100. package/src/zexus/renderer/__init__.py +61 -0
  101. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  103. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  104. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  105. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  106. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  107. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  108. package/src/zexus/renderer/backend.py +261 -0
  109. package/src/zexus/renderer/canvas.py +78 -0
  110. package/src/zexus/renderer/color_system.py +201 -0
  111. package/src/zexus/renderer/graphics.py +31 -0
  112. package/src/zexus/renderer/layout.py +222 -0
  113. package/src/zexus/renderer/main_renderer.py +66 -0
  114. package/src/zexus/renderer/painter.py +30 -0
  115. package/src/zexus/renderer/tk_backend.py +208 -0
  116. package/src/zexus/renderer/web_backend.py +260 -0
  117. package/src/zexus/runtime/__init__.py +10 -2
  118. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  120. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  121. package/src/zexus/runtime/file_flags.py +137 -0
  122. package/src/zexus/runtime/load_manager.py +368 -0
  123. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  125. package/src/zexus/security.py +424 -34
  126. package/src/zexus/stdlib/fs.py +23 -18
  127. package/src/zexus/stdlib/http.py +289 -186
  128. package/src/zexus/stdlib/sockets.py +207 -163
  129. package/src/zexus/stdlib/websockets.py +282 -0
  130. package/src/zexus/stdlib_integration.py +369 -2
  131. package/src/zexus/strategy_recovery.py +6 -3
  132. package/src/zexus/type_checker.py +423 -0
  133. package/src/zexus/virtual_filesystem.py +189 -2
  134. package/src/zexus/vm/__init__.py +113 -3
  135. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  136. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  137. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  138. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  139. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  140. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  141. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  142. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  143. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  144. package/src/zexus/vm/async_optimizer.py +80 -6
  145. package/src/zexus/vm/binary_bytecode.py +659 -0
  146. package/src/zexus/vm/bytecode.py +59 -11
  147. package/src/zexus/vm/bytecode_converter.py +26 -12
  148. package/src/zexus/vm/cabi.c +1985 -0
  149. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  150. package/src/zexus/vm/cabi.h +127 -0
  151. package/src/zexus/vm/cache.py +561 -17
  152. package/src/zexus/vm/compiler.py +818 -51
  153. package/src/zexus/vm/fastops.c +15743 -0
  154. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  155. package/src/zexus/vm/fastops.pyx +288 -0
  156. package/src/zexus/vm/gas_metering.py +50 -9
  157. package/src/zexus/vm/jit.py +364 -20
  158. package/src/zexus/vm/native_jit_backend.py +1816 -0
  159. package/src/zexus/vm/native_runtime.cpp +1388 -0
  160. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  161. package/src/zexus/vm/optimizer.py +161 -11
  162. package/src/zexus/vm/parallel_vm.py +140 -45
  163. package/src/zexus/vm/peephole_optimizer.py +82 -4
  164. package/src/zexus/vm/profiler.py +38 -18
  165. package/src/zexus/vm/register_allocator.py +16 -5
  166. package/src/zexus/vm/register_vm.py +8 -5
  167. package/src/zexus/vm/vm.py +3581 -531
  168. package/src/zexus/vm/wasm_compiler.py +658 -0
  169. package/src/zexus/zexus_ast.py +137 -11
  170. package/src/zexus/zexus_token.py +16 -5
  171. package/src/zexus/zpm/installer.py +55 -15
  172. package/src/zexus/zpm/package_manager.py +1 -1
  173. package/src/zexus/zpm/registry.py +257 -28
  174. package/src/zexus.egg-info/PKG-INFO +16 -6
  175. package/src/zexus.egg-info/SOURCES.txt +129 -17
  176. package/src/zexus.egg-info/entry_points.txt +1 -0
  177. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,78 @@
1
+ """Lightweight drawing canvas used for renderer CANVAS helpers."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import Dict, List, Tuple
6
+
7
+ from .color_system import RGBColor
8
+
9
+
10
+ @dataclass
11
+ class Canvas:
12
+ width: int
13
+ height: int
14
+ pixels: List[List[str]] = field(init=False)
15
+ colours: List[List[RGBColor | None]] = field(init=False)
16
+ operations: List[Tuple[str, Tuple[object, ...]]] = field(default_factory=list)
17
+
18
+ def __post_init__(self) -> None:
19
+ self.pixels = [[" " for _ in range(self.width)] for _ in range(self.height)]
20
+ self.colours = [[None for _ in range(self.width)] for _ in range(self.height)]
21
+
22
+ # ------------------------------------------------------------------
23
+ def draw_line(self, x1: int, y1: int, x2: int, y2: int, *, char: str = "█", colour: RGBColor | None = None) -> None:
24
+ orig = (x1, y1, x2, y2)
25
+ dx = abs(x2 - x1)
26
+ dy = -abs(y2 - y1)
27
+ sx = 1 if x1 < x2 else -1
28
+ sy = 1 if y1 < y2 else -1
29
+ err = dx + dy
30
+ while True:
31
+ self._plot(x1, y1, char, colour)
32
+ if x1 == x2 and y1 == y2:
33
+ break
34
+ e2 = 2 * err
35
+ if e2 >= dy:
36
+ err += dy
37
+ x1 += sx
38
+ if e2 <= dx:
39
+ err += dx
40
+ y1 += sy
41
+ self.operations.append(("line", orig))
42
+
43
+ def draw_text(self, x: int, y: int, text: str, *, colour: RGBColor | None = None) -> None:
44
+ for offset, ch in enumerate(text):
45
+ self._plot(x + offset, y, ch, colour)
46
+ self.operations.append(("text", (x, y, text)))
47
+
48
+ def snapshot(self) -> Dict[str, object]:
49
+ return {
50
+ "width": self.width,
51
+ "height": self.height,
52
+ "pixels": ["".join(row) for row in self.pixels],
53
+ "draw_ops": list(self.operations),
54
+ }
55
+
56
+ # ------------------------------------------------------------------
57
+ def _plot(self, x: int, y: int, char: str, colour: RGBColor | None) -> None:
58
+ if 0 <= x < self.width and 0 <= y < self.height:
59
+ self.pixels[y][x] = char
60
+ self.colours[y][x] = colour
61
+
62
+
63
+ @dataclass
64
+ class CanvasRegistry:
65
+ canvases: Dict[str, Canvas] = field(default_factory=dict)
66
+
67
+ def create(self, *, width: int, height: int) -> str:
68
+ identifier = f"canvas_{len(self.canvases) + 1}"
69
+ self.canvases[identifier] = Canvas(width, height)
70
+ return identifier
71
+
72
+ def get(self, identifier: str) -> Canvas:
73
+ if identifier not in self.canvases:
74
+ raise KeyError(f"canvas '{identifier}' not found")
75
+ return self.canvases[identifier]
76
+
77
+ def snapshot(self) -> Dict[str, Dict[str, object]]:
78
+ return {name: canvas.snapshot() for name, canvas in self.canvases.items()}
@@ -0,0 +1,201 @@
1
+ """Colour utilities for the Zexus renderer.
2
+
3
+ The previous implementation lived at ``renderer/color_system.py``. The new
4
+ version adopts dataclasses, immutability and richer typing to make the colour
5
+ pipeline easier to reason about and safer to share between the interpreter and
6
+ VM backends.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Dict, Iterable, List, Tuple
12
+
13
+
14
+ def _clamp(value: float, lo: float, hi: float) -> float:
15
+ return max(lo, min(hi, value))
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class RGBColor:
20
+ """Simple immutable RGB colour representation."""
21
+
22
+ r: int
23
+ g: int
24
+ b: int
25
+
26
+ def __post_init__(self) -> None:
27
+ object.__setattr__(self, "r", int(_clamp(self.r, 0, 255)))
28
+ object.__setattr__(self, "g", int(_clamp(self.g, 0, 255)))
29
+ object.__setattr__(self, "b", int(_clamp(self.b, 0, 255)))
30
+
31
+ # ------------------------------------------------------------------
32
+ # Blending helpers
33
+ # ------------------------------------------------------------------
34
+ def mix(self, other: "RGBColor", ratio: float = 0.5) -> "RGBColor":
35
+ ratio = _clamp(ratio, 0.0, 1.0)
36
+ inv = 1.0 - ratio
37
+ return RGBColor(
38
+ int(self.r * inv + other.r * ratio),
39
+ int(self.g * inv + other.g * ratio),
40
+ int(self.b * inv + other.b * ratio),
41
+ )
42
+
43
+ def lighten(self, amount: float = 0.1) -> "RGBColor":
44
+ return self.mix(RGBColor(255, 255, 255), amount)
45
+
46
+ def darken(self, amount: float = 0.1) -> "RGBColor":
47
+ return self.mix(RGBColor(0, 0, 0), amount)
48
+
49
+ # ------------------------------------------------------------------
50
+ # Conversions
51
+ # ------------------------------------------------------------------
52
+ def to_hsv(self) -> Tuple[float, float, float]:
53
+ r, g, b = self.r / 255.0, self.g / 255.0, self.b / 255.0
54
+ c_max = max(r, g, b)
55
+ c_min = min(r, g, b)
56
+ delta = c_max - c_min
57
+
58
+ if delta == 0:
59
+ hue = 0.0
60
+ elif c_max == r:
61
+ hue = (60 * ((g - b) / delta)) % 360
62
+ elif c_max == g:
63
+ hue = 60 * (((b - r) / delta) + 2)
64
+ else:
65
+ hue = 60 * (((r - g) / delta) + 4)
66
+
67
+ saturation = 0.0 if c_max == 0 else delta / c_max
68
+ value = c_max
69
+ return hue, saturation, value
70
+
71
+ @staticmethod
72
+ def from_hsv(h: float, s: float, v: float) -> "RGBColor":
73
+ h = h % 360
74
+ s = _clamp(s, 0.0, 1.0)
75
+ v = _clamp(v, 0.0, 1.0)
76
+
77
+ c = v * s
78
+ x = c * (1 - abs(((h / 60.0) % 2) - 1))
79
+ m = v - c
80
+
81
+ if 0 <= h < 60:
82
+ r, g, b = c, x, 0
83
+ elif 60 <= h < 120:
84
+ r, g, b = x, c, 0
85
+ elif 120 <= h < 180:
86
+ r, g, b = 0, c, x
87
+ elif 180 <= h < 240:
88
+ r, g, b = 0, x, c
89
+ elif 240 <= h < 300:
90
+ r, g, b = x, 0, c
91
+ else:
92
+ r, g, b = c, 0, x
93
+
94
+ return RGBColor(int((r + m) * 255), int((g + m) * 255), int((b + m) * 255))
95
+
96
+ # ------------------------------------------------------------------
97
+ def to_ansi(self, *, background: bool = False) -> str:
98
+ code = 48 if background else 38
99
+ return f"\033[{code};2;{self.r};{self.g};{self.b}m"
100
+
101
+ def __str__(self) -> str: # pragma: no cover - human-friendly repr
102
+ return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
103
+
104
+
105
+ class ColorPalette:
106
+ """Palette of named colours with simple mixing helpers."""
107
+
108
+ def __init__(self) -> None:
109
+ self._base = self._build_base_palette()
110
+ self._custom: Dict[str, RGBColor] = {}
111
+ self._gradients: Dict[str, List[RGBColor]] = {}
112
+
113
+ @staticmethod
114
+ def _build_base_palette() -> Dict[str, RGBColor]:
115
+ values = {
116
+ "red": (255, 59, 48),
117
+ "orange": (255, 149, 0),
118
+ "yellow": (255, 204, 0),
119
+ "green": (52, 199, 89),
120
+ "mint": (0, 199, 190),
121
+ "teal": (48, 176, 199),
122
+ "cyan": (50, 173, 230),
123
+ "blue": (0, 122, 255),
124
+ "indigo": (88, 86, 214),
125
+ "purple": (175, 82, 222),
126
+ "pink": (255, 45, 85),
127
+ "brown": (162, 132, 94),
128
+ "white": (255, 255, 255),
129
+ "gray": (142, 142, 147),
130
+ "gray2": (174, 174, 178),
131
+ "gray3": (199, 199, 204),
132
+ "gray4": (209, 209, 214),
133
+ "gray5": (229, 229, 234),
134
+ "gray6": (242, 242, 247),
135
+ "black": (0, 0, 0),
136
+ }
137
+ base = {name: RGBColor(*rgb) for name, rgb in values.items()}
138
+ for name, colour in list(base.items()):
139
+ base[f"light_{name}"] = colour.lighten(0.3)
140
+ base[f"dark_{name}"] = colour.darken(0.3)
141
+ return base
142
+
143
+ # ------------------------------------------------------------------
144
+ def get(self, name: str) -> RGBColor:
145
+ if name in self._base:
146
+ return self._base[name]
147
+ if name in self._custom:
148
+ return self._custom[name]
149
+ raise KeyError(f"unknown colour '{name}'")
150
+
151
+ def define(self, name: str, colour: RGBColor) -> None:
152
+ self._custom[name] = colour
153
+
154
+ def mix(self, colour_a: str, colour_b: str, ratio: float = 0.5) -> RGBColor:
155
+ colour = self.get(colour_a).mix(self.get(colour_b), ratio)
156
+ return colour
157
+
158
+ def gradient(self, start: str, end: str, steps: int, *, name: str | None = None) -> List[RGBColor]:
159
+ steps = max(2, steps)
160
+ start_colour = self.get(start)
161
+ end_colour = self.get(end)
162
+ values = [start_colour.mix(end_colour, i / (steps - 1)) for i in range(steps)]
163
+ if name:
164
+ self._gradients[name] = values
165
+ return values
166
+
167
+ def list_names(self) -> Iterable[str]:
168
+ yield from self._base.keys()
169
+ yield from self._custom.keys()
170
+
171
+
172
+ class Theme:
173
+ """Collection of palette-derived colours."""
174
+
175
+ def __init__(self, name: str, palette: ColorPalette) -> None:
176
+ self.name = name
177
+ self._palette = palette
178
+ self._colours: Dict[str, RGBColor] = {}
179
+
180
+ def set_colour(self, key: str, colour_name: str) -> None:
181
+ self._colours[key] = self._palette.get(colour_name)
182
+
183
+ def derive_from(self, colour_name: str) -> None:
184
+ base = self._palette.get(colour_name)
185
+ self._colours.update(
186
+ {
187
+ "primary": base,
188
+ "primary_light": base.lighten(0.2),
189
+ "primary_dark": base.darken(0.2),
190
+ }
191
+ )
192
+
193
+ def get(self, key: str, default: str | None = None) -> RGBColor:
194
+ if key in self._colours:
195
+ return self._colours[key]
196
+ if default:
197
+ return self._palette.get(default)
198
+ return self._palette.get("black")
199
+
200
+ def snapshot(self) -> Dict[str, str]:
201
+ return {key: str(colour) for key, colour in self._colours.items()}
@@ -0,0 +1,31 @@
1
+ """Utility helpers for higher-level graphics composition.
2
+
3
+ The original module exposed a number of free-form helpers. For now we provide
4
+ lightweight primitives that are sufficient for interpreter driven demos while
5
+ keeping the door open for future expansion.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Iterable, List
10
+
11
+
12
+ def merge_layers(layers: Iterable[List[str]]) -> List[str]:
13
+ """Merge multiple ASCII layers, preferring non-space characters."""
14
+ merged: List[str] = []
15
+ for layer in layers:
16
+ if not merged:
17
+ merged = list(layer)
18
+ continue
19
+ merged = [
20
+ "".join(c2 if c2 != " " else c1 for c1, c2 in zip(row1, row2))
21
+ for row1, row2 in zip(merged, layer)
22
+ ]
23
+ return merged
24
+
25
+
26
+ def frame(text: str, *, padding: int = 1) -> str:
27
+ lines = text.splitlines() or [""]
28
+ width = max(len(line) for line in lines)
29
+ padded = [" " * padding + line.ljust(width) + " " * padding for line in lines]
30
+ horizontal = "─" * (width + padding * 2)
31
+ return "\n".join([f"┌{horizontal}┐", *[f"│{line}│" for line in padded], f"└{horizontal}┘"])
@@ -0,0 +1,222 @@
1
+ """Screen/component hierarchy used by the renderer backend.
2
+
3
+ The API mirrors the original ``renderer.layout`` module but embraces dataclasses
4
+ and type hints. Rendering still produces ASCII output so existing snapshot
5
+ based tests continue to work.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Dict, Iterable, List, Optional
11
+
12
+
13
+ @dataclass
14
+ class ScreenComponent:
15
+ name: str
16
+ properties: Dict[str, object] = field(default_factory=dict)
17
+ children: List["ScreenComponent"] = field(default_factory=list)
18
+ parent: Optional["ScreenComponent"] = None
19
+ type: str = "component"
20
+
21
+ def add_child(self, component: "ScreenComponent") -> None:
22
+ component.parent = self
23
+ self.children.append(component)
24
+
25
+ # ------------------------------------------------------------------
26
+ def get(self, key: str, default: object | None = None) -> object | None:
27
+ if key in self.properties:
28
+ return self.properties[key]
29
+ if self.parent:
30
+ return self.parent.get(key, default)
31
+ return default
32
+
33
+ def clone(self, name: Optional[str] = None) -> "ScreenComponent":
34
+ copied = type(self)(name or self.name, **dict(self.properties))
35
+ for child in self.children:
36
+ copied.add_child(child.clone())
37
+ return copied
38
+
39
+ # Rendering ---------------------------------------------------------
40
+ def render(self, width: int, height: int) -> List[str]: # pragma: no cover - overridden
41
+ raise NotImplementedError
42
+
43
+
44
+ @dataclass
45
+ class Screen(ScreenComponent):
46
+ type: str = "screen"
47
+
48
+ def __post_init__(self) -> None:
49
+ self.properties.setdefault("width", 80)
50
+ self.properties.setdefault("height", 24)
51
+ self.properties.setdefault("border", True)
52
+ self.properties.setdefault("title", "")
53
+
54
+ def render(self, width: int, height: int) -> List[str]:
55
+ screen_width = int(self.get("width", width))
56
+ screen_height = int(self.get("height", height))
57
+ show_border = bool(self.get("border", True))
58
+
59
+ canvas = [[" " for _ in range(screen_width)] for _ in range(screen_height)]
60
+
61
+ if show_border:
62
+ _draw_border(canvas)
63
+
64
+ title = str(self.get("title", ""))
65
+ if title:
66
+ _draw_title(canvas, title)
67
+
68
+ for child in self.children:
69
+ child_canvas = child.render(screen_width - 4, screen_height - 4)
70
+ origin_x = int(child.get("x", 2))
71
+ origin_y = int(child.get("y", 2))
72
+ _blit(canvas, child_canvas, origin_x, origin_y)
73
+
74
+ return ["".join(row) for row in canvas]
75
+
76
+
77
+ @dataclass
78
+ class Button(ScreenComponent):
79
+ type: str = "button"
80
+
81
+ def __post_init__(self) -> None:
82
+ defaults = {
83
+ "text": "Button",
84
+ "width": 14,
85
+ "height": 3,
86
+ "enabled": True,
87
+ "x": 0,
88
+ "y": 0,
89
+ }
90
+ for key, value in defaults.items():
91
+ self.properties.setdefault(key, value)
92
+
93
+ def render(self, width: int, height: int) -> List[str]:
94
+ w = max(3, int(self.get("width", 14)))
95
+ h = max(3, int(self.get("height", 3)))
96
+ text = str(self.get("text", "Button"))[: w - 2]
97
+ enabled = bool(self.get("enabled", True))
98
+ border = ("┌", "┐", "└", "┘", "─", "│") if enabled else ("+", "+", "+", "+", "-", "|")
99
+
100
+ lines = [border[0] + border[4] * (w - 2) + border[1]]
101
+ lines.append(border[5] + text.center(w - 2) + border[5])
102
+ lines.append(border[2] + border[4] * (w - 2) + border[3])
103
+ while len(lines) < h:
104
+ lines.append(" " * w)
105
+ return lines
106
+
107
+
108
+ @dataclass
109
+ class TextBox(ScreenComponent):
110
+ type: str = "textbox"
111
+
112
+ def __post_init__(self) -> None:
113
+ defaults = {
114
+ "text": "",
115
+ "placeholder": "",
116
+ "width": 20,
117
+ "height": 3,
118
+ "x": 0,
119
+ "y": 0,
120
+ }
121
+ for key, value in defaults.items():
122
+ self.properties.setdefault(key, value)
123
+
124
+ def render(self, width: int, height: int) -> List[str]:
125
+ w = max(4, int(self.get("width", 20)))
126
+ h = max(3, int(self.get("height", 3)))
127
+ text = str(self.get("text", "")) or str(self.get("placeholder", ""))
128
+ if len(text) > w - 4:
129
+ text = f"{text[: w - 7]}..."
130
+
131
+ lines = ["┌" + "─" * (w - 2) + "┐"]
132
+ lines.append("│ " + text.ljust(w - 4) + " │")
133
+ lines.append("└" + "─" * (w - 2) + "┘")
134
+ while len(lines) < h:
135
+ lines.append(" " * w)
136
+ return lines
137
+
138
+
139
+ @dataclass
140
+ class Label(ScreenComponent):
141
+ type: str = "label"
142
+
143
+ def __post_init__(self) -> None:
144
+ self.properties.setdefault("text", "")
145
+ self.properties.setdefault("x", 0)
146
+ self.properties.setdefault("y", 0)
147
+
148
+ def render(self, width: int, height: int) -> List[str]:
149
+ return [str(self.get("text", ""))]
150
+
151
+
152
+ class ScreenRegistry:
153
+ def __init__(self) -> None:
154
+ self._screens: Dict[str, Screen] = {}
155
+ self._components: Dict[str, ScreenComponent] = {}
156
+
157
+ # ------------------------------------------------------------------
158
+ def register_screen(self, screen: Screen) -> None:
159
+ self._screens[screen.name] = screen
160
+
161
+ def register_component(self, component: ScreenComponent) -> None:
162
+ self._components[component.name] = component
163
+
164
+ def get_screen(self, name: str) -> Screen:
165
+ if name not in self._screens:
166
+ raise KeyError(f"screen '{name}' not defined")
167
+ return self._screens[name]
168
+
169
+ def get_component(self, name: str) -> ScreenComponent:
170
+ if name not in self._components:
171
+ raise KeyError(f"component '{name}' not defined")
172
+ return self._components[name]
173
+
174
+ def clone_screen(self, name: str) -> Screen:
175
+ return self.get_screen(name).clone()
176
+
177
+ def list_screens(self) -> Iterable[str]:
178
+ return list(self._screens.keys())
179
+
180
+ def list_components(self) -> Iterable[str]:
181
+ return list(self._components.keys())
182
+
183
+ def snapshot(self) -> Dict[str, Screen]:
184
+ return dict(self._screens)
185
+
186
+ def component_snapshot(self) -> Dict[str, ScreenComponent]:
187
+ return dict(self._components)
188
+
189
+
190
+ # Internal helpers ------------------------------------------------------
191
+
192
+ def _draw_border(canvas: List[List[str]]) -> None:
193
+ width = len(canvas[0])
194
+ height = len(canvas)
195
+ canvas[0][0] = "╭"
196
+ canvas[0][width - 1] = "╮"
197
+ canvas[height - 1][0] = "╰"
198
+ canvas[height - 1][width - 1] = "╯"
199
+ for x in range(1, width - 1):
200
+ canvas[0][x] = "─"
201
+ canvas[height - 1][x] = "─"
202
+ for y in range(1, height - 1):
203
+ canvas[y][0] = "│"
204
+ canvas[y][width - 1] = "│"
205
+
206
+
207
+ def _draw_title(canvas: List[List[str]], title: str) -> None:
208
+ width = len(canvas[0])
209
+ title = f" {title} "
210
+ title = title if len(title) < width - 2 else title[: width - 5] + "..."
211
+ offset = max(1, (width - len(title)) // 2)
212
+ for i, ch in enumerate(title):
213
+ canvas[0][offset + i] = ch
214
+
215
+
216
+ def _blit(canvas: List[List[str]], child: List[str], x: int, y: int) -> None:
217
+ for row, line in enumerate(child):
218
+ for col, ch in enumerate(line):
219
+ target_y = y + row
220
+ target_x = x + col
221
+ if 0 <= target_y < len(canvas) and 0 <= target_x < len(canvas[0]) and ch != " ":
222
+ canvas[target_y][target_x] = ch
@@ -0,0 +1,66 @@
1
+ """High-level renderer facade used by the backend."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Dict
5
+
6
+ from .layout import Button, Label, Screen, ScreenComponent, ScreenRegistry, TextBox
7
+ from .painter import AsciiPainter
8
+
9
+
10
+ _COMPONENT_TYPES: Dict[str, type[ScreenComponent]] = {
11
+ "button": Button,
12
+ "textbox": TextBox,
13
+ "label": Label,
14
+ "screen": Screen,
15
+ }
16
+
17
+
18
+ class ZexusScreenRenderer:
19
+ def __init__(self, registry: ScreenRegistry | None = None) -> None:
20
+ self.registry = registry or ScreenRegistry()
21
+ self.painter = AsciiPainter()
22
+ self._install_default_components()
23
+
24
+ def _install_default_components(self) -> None:
25
+ if "primary_button" not in self.registry.list_components():
26
+ self.define_component("primary_button", "button", text="Submit", color="blue")
27
+ if "secondary_button" not in self.registry.list_components():
28
+ self.define_component("secondary_button", "button", text="Cancel", color="gray")
29
+
30
+ # ------------------------------------------------------------------
31
+ def define_screen(self, name: str, **properties) -> Screen:
32
+ screen = Screen(name)
33
+ if properties:
34
+ screen.properties.update(properties)
35
+ self.registry.register_screen(screen)
36
+ return screen
37
+
38
+ def define_component(self, name: str, component_type: str | None = None, **properties) -> ScreenComponent:
39
+ if component_type is None:
40
+ component_type = str(properties.pop("type", "label"))
41
+ if component_type not in _COMPONENT_TYPES:
42
+ raise ValueError(f"unknown component type '{component_type}'")
43
+ component = _COMPONENT_TYPES[component_type](name)
44
+ if properties:
45
+ component.properties.update(properties)
46
+ self.registry.register_component(component)
47
+ return component
48
+
49
+ def add_to_screen(self, screen_name: str, component_name: str, **overrides) -> None:
50
+ screen = self.registry.get_screen(screen_name)
51
+ component = self.registry.get_component(component_name)
52
+ clone = component.clone()
53
+ clone.properties.update(overrides)
54
+ screen.add_child(clone)
55
+
56
+ def render_screen(self, screen_name: str, **overrides) -> str:
57
+ screen = self.registry.clone_screen(screen_name)
58
+ screen.properties.update(overrides)
59
+
60
+ width = int(screen.get("width", 80))
61
+ height = int(screen.get("height", 24))
62
+ self.painter.init_screen(width, height)
63
+
64
+ rendered_lines = screen.render(width, height)
65
+ self.painter.draw_lines(rendered_lines, 0, 0)
66
+ return self.painter.render()
@@ -0,0 +1,30 @@
1
+ """ASCII painter used by ``main_renderer``."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import List
6
+
7
+
8
+ @dataclass
9
+ class AsciiPainter:
10
+ width: int = 0
11
+ height: int = 0
12
+ buffer: List[List[str]] = field(default_factory=list)
13
+
14
+ def init_screen(self, width: int, height: int) -> None:
15
+ self.width = width
16
+ self.height = height
17
+ self.buffer = [[" " for _ in range(width)] for _ in range(height)]
18
+
19
+ def draw_lines(self, lines: List[str], x: int, y: int) -> None:
20
+ for row, line in enumerate(lines):
21
+ target_y = y + row
22
+ if not (0 <= target_y < self.height):
23
+ continue
24
+ for col, char in enumerate(line):
25
+ target_x = x + col
26
+ if 0 <= target_x < self.width and char != " ":
27
+ self.buffer[target_y][target_x] = char
28
+
29
+ def render(self) -> str:
30
+ return "\n".join("".join(row) for row in self.buffer)